~comcloudway/melon

81b551791f8b86f37a1428c3a68121949198e437 — Jakob Meier 6 months ago 128cc3b
pause videos by default unless in playlist mode

BUG: setting playback position appears to break peertube playback
the smae thing also appears to happend with invidious on slow
conenctions
M melon/models/__init__.py => melon/models/__init__.py +11 -0
@@ 725,6 725,17 @@ def get_last_feed_refresh() -> int:
        return app_conf["news-feed-refresh"]
    return None

def get_video_duration(server_id:str, video_id:str) -> (float|None):
    conn = connect_to_db()
    results = conn.execute("""
    SELECT duration
    FROM playback
    WHERE video_id = ? AND video_server = ?
    """, (video_id, server_id)).fetchone()
    if results is None:
        return None
    return results[0]

def get_video_playback_position(server_id:str, video_id:str) -> (float|None):
    conn = connect_to_db()
    results = conn.execute("""

M melon/player/__init__.py => melon/player/__init__.py +17 -6
@@ 13,7 13,7 @@ from melon.widgets.feeditem import AdaptiveFeedItem
from melon.widgets.preferencerow import PreferenceRow, PreferenceType, Preference
from melon.widgets.iconbutton import IconButton
from melon.models import get_app_settings, add_to_history, register_callback
from melon.models import get_video_playback_position, set_video_playback_position, set_video_playback_position_force
from melon.models import get_video_playback_position, set_video_playback_position, set_video_playback_position_force, get_video_duration
from melon.utils import pass_me

class PlayerScreen(Adw.NavigationPage):


@@ 151,7 151,7 @@ class PlayerScreen(Adw.NavigationPage):

        # reset playback position if video was nearly completely watched
        pos = get_video_playback_position(server_id, video_id)
        dur = get_video_playback_position(server_id, video_id)
        dur = get_video_duration(server_id, video_id)
        if not pos is None and not dur is None:
            # TODO consider moving this to the settings panel
            consider_done = 0.99*dur


@@ 219,9 219,15 @@ class PlayerScreen(Adw.NavigationPage):
                self.position = pos
            else:
                self.position = None
            # get video duration & update db
            # handled by self.on_duration
            self.instance.get_video_duration(self.view, self.on_duration)
            self.prepare_player_state()

    def prepare_player_state(self):
        self.instance.do_video_pause(self.view, self.prepared_player_state())

    def prepared_player_state(self):
        # get video duration & update db
        # handled by self.on_duration
        self.instance.get_video_duration(self.view, self.on_duration)

    def on_duration(self, duration):
        self.duration = duration


@@ 231,6 237,7 @@ class PlayerScreen(Adw.NavigationPage):
        else:
            self.on_player_setup_done()
    def on_player_setup_done(self):
        # save data
        self.commit_playback()
        # connect to events
        self.connect("hiding", lambda _: self.on_hide())


@@ 267,7 274,11 @@ class PlayerScreen(Adw.NavigationPage):
    def store_position(self, after=None, commit=True):
        self.instance.get_video_playback_position(self.view, pass_me(self.on_position, after, commit))
    def on_position(self, position, after=None, commit=True):
        self.position = position
        if not position is None:
            # only save valid datasets
            self.position = position
        # because this often occurs when killing the webview
        # or on_hide, we still want to commit the changes
        if commit:
            self.commit_playback()
        if not after is None:

M melon/player/playlist.py => melon/player/playlist.py +7 -0
@@ 289,3 289,10 @@ class PlaylistPlayerScreen(PlayerScreen):
        self.ctr_group.add(expander)

        self.about.add(self.ctr_group)

    def prepare_player_state(self):
        # by default the player pauses the video
        # so that the user has to click "play"
        # however in playlist mode, we kind of want videos to autoplay
        # especially after the first one
        self.instance.do_video_play(self.view, self.prepared_player_state())

M melon/servers/__init__.py => melon/servers/__init__.py +16 -0
@@ 294,3 294,19 @@ class Server(ABC):
        should pass False to onDone if there was an error
        """
        on_done(False)


    def do_video_play(self, webview: WebKit.WebView, on_done: Callable[[bool], None]):
        """
        Should attempt to start playing the video
        i.e by calling the .play() method via javascript
        if that worked out True should be passed to on_done
        """
        on_done(False)
    def do_video_pause(self, webview: WebKit.WebView, on_done: Callable[[bool], None]):
        """
        Should attempt to stop playing the video
        i.e by calling the .pause() method via javascript
        if that worked out True should be passed to on_done
        """
        on_done(False)

M melon/servers/invidious/__init__.py => melon/servers/invidious/__init__.py +49 -0
@@ 332,6 332,20 @@ class Invidious(Server):
                });
            });
        }
        let readyState = 2;
        if (player.readyState < readyState) {
            await new Promise(res=>{
                if (player.readyState >= readyState) { res(); return; }
                let list = player.addEventListener(
                    'loadeddata',
                    ()=>{
                        if (player.readyState >= readyState) {
                            player.removeEventListener('loadeddata', list);
                            res();
                        }
                });
            });
        }
    """

    def get_video_playback_position(self, webview, on_data):


@@ 417,6 431,41 @@ class Invidious(Server):
            pass_me(self.on_js_bool, webview, on_done)
        )

    def do_video_play(self, webview, on_done):
        js = f"""
        let players = document.getElementsByTagName('video');
        if (players.length != 1) return false;
        let player = players[0];
        {self.js_player_ready}
        await player.play();
        return true;
        """
        webview.call_async_javascript_function(
            js, len(js),
            None,
            None,
            self.id,
            None,
            pass_me(self.on_js_bool, webview, on_done)
        )
    def do_video_pause(self, webview, on_done):
        js = f"""
        let players = document.getElementsByTagName('video');
        if (players.length != 1) return false;
        let player = players[0];
        {self.js_player_ready}
        player.pause();
        return true;
        """
        webview.call_async_javascript_function(
            js, len(js),
            None,
            None,
            self.id,
            None,
            pass_me(self.on_js_bool, webview, on_done)
        )

    def on_js_bool(self, obj, async_res, webview, on_bool):
        if on_bool is None:
            return

M melon/servers/peertube/__init__.py => melon/servers/peertube/__init__.py +66 -11
@@ 295,16 295,22 @@ class Peertube(Server):
        results = []
        if r.ok:
            for item in r.json()["data"]:
                item = item["video"]
                video_host = instance
                video_slug = item["uuid"]
                video_id=f"{video_host}::{video_slug}"
                thumb_path = item["thumbnailPath"]
                thumb = f"{instance}{thumb_path}"
                channel_name = item["channel"]["displayName"]
                channel_slug = item["channel"]["name"]
                channel_host = item["channel"]["host"]
                channel_id = f"https://{channel_host}::{channel_slug}"
                try:
                    item = item["video"]
                    video_host = instance
                    video_slug = item["uuid"]
                    video_id=f"{video_host}::{video_slug}"
                    thumb_path = item["thumbnailPath"]
                    thumb = f"{instance}{thumb_path}"
                    channel_name = item["channel"]["displayName"]
                    channel_slug = item["channel"]["name"]
                    channel_host = item["channel"]["host"]
                    channel_id = f"https://{channel_host}::{channel_slug}"
                except Exception as e:
                    # skip invaldi entries
                    # most likely a deleted video
                    # which we can't display
                    continue
                v = Video(
                    self.id,
                    item["url"],


@@ 389,7 395,7 @@ class Peertube(Server):
    js_player_ready = """
        if (isNaN(player.duration)) {
            await new Promise((res)=>{
                if (!isNaN(player.duration)) { res(); return undefined; }
                if (!isNaN(player.duration)) { res(); return; }
                let list = player.addEventListener(
                    'loadedmetadata',
                    ()=>{


@@ 398,6 404,20 @@ class Peertube(Server):
                });
            });
        }
        let readyState = 2;
        if (player.readyState < readyState) {
            await new Promise(res=>{
                if (player.readyState >= readyState) { res(); return; }
                let list = player.addEventListener(
                    'loadeddata',
                    ()=>{
                        if (player.readyState >= readyState) {
                            player.removeEventListener('loadeddata', list);
                            res();
                        }
                });
            });
        }
    """

    js_has_player = """


@@ 516,6 536,41 @@ class Peertube(Server):
            pass_me(self.on_js_bool, webview, on_done)
        )

    def do_video_play(self, webview, on_done):
        js = f"""
        {self.js_has_player}
        let players = document.getElementsByTagName('video');
        if (players.length != 1) return false;
        let player = players[0];
        await player.play();
        return true;
        """
        webview.call_async_javascript_function(
            js, len(js),
            None,
            None,
            self.id,
            None,
            pass_me(self.on_js_bool, webview, on_done)
        )
    def do_video_pause(self, webview, on_done):
        js = f"""
        {self.js_has_player}
        let players = document.getElementsByTagName('video');
        if (players.length != 1) return false;
        let player = players[0];
        player.pause();
        return true;
        """
        webview.call_async_javascript_function(
            js, len(js),
            None,
            None,
            self.id,
            None,
            pass_me(self.on_js_bool, webview, on_done)
        )

    def on_js_bool(self, obj, async_res, webview, on_bool):
        if on_bool is None:
            return