~comcloudway/melon

128cc3b2e3636e229b5ef2afac9586e67bcda2c9 — Jakob Meier 6 months ago c58675f
initial webview control bridge support for peertube
2 files changed, 164 insertions(+), 2 deletions(-)

M melon/servers/invidious/__init__.py
M melon/servers/peertube/__init__.py
M melon/servers/invidious/__init__.py => melon/servers/invidious/__init__.py +0 -2
@@ 7,7 7,6 @@ import gi
gi.require_version("WebKit", "6.0")
from gi.repository import GLib, WebKit


from melon.import_providers.newpipe import NewpipeImporter
from melon.servers import Server, Preference, PreferenceType
from melon.servers import Feed, Channel, Video, Playlist, Stream, SearchMode


@@ 322,7 321,6 @@ class Invidious(Server):
    WEBVIEW_READY = WebKit.LoadEvent.COMMITTED

    js_player_ready = """
        let min_ready_state = 3;
        if (isNaN(player.duration)) {
            await new Promise((res)=>{
                if (!isNaN(player.duration)) { res(); return undefined; }

M melon/servers/peertube/__init__.py => melon/servers/peertube/__init__.py +164 -0
@@ 2,11 2,15 @@ import requests
from urllib.parse import urlparse,parse_qs
from datetime import datetime
from gettext import gettext as _
import gi
gi.require_version("WebKit", "6.0")
from gi.repository import GLib, WebKit

from melon.servers import Server, Preference, PreferenceType
from melon.servers import Feed, Channel, Video, Playlist, Stream, SearchMode
from melon.servers import USER_AGENT
from melon.import_providers.newpipe import NewpipeImporter
from melon.utils import pass_me

class Peertube(Server):



@@ 376,3 380,163 @@ class Peertube(Server):
            # TODO: port newpipe invidious importer to peertube
            # REQUIRES: figuring out what service_id peertube uses
        ]

    #
    # WEBVIEW CONTROL BRIDGE
    #
    WEBVIEW_READY = WebKit.LoadEvent.FINISHED

    js_player_ready = """
        if (isNaN(player.duration)) {
            await new Promise((res)=>{
                if (!isNaN(player.duration)) { res(); return undefined; }
                let list = player.addEventListener(
                    'loadedmetadata',
                    ()=>{
                        player.removeEventListener('loadedmetadata', list);
                        res();
                });
            });
        }
    """

    js_has_player = """
    let selector = '#vjs_video_3_html5_api';
    if (!document.querySelector(selector)) {
        await new Promise(resolve => {
            if (document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                return;
            }

            const observer = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });

            if (document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                return;
            }

            observer.observe(document.body, {
                subtree: true,
                childList: true,
            });
        });
    }
    """

    def get_video_playback_position(self, webview, on_data):
        js = f"""
        {self.js_has_player}
        let players = document.getElementsByTagName('video');
        if (players.length != 1) return undefined;
        let player = players[0];
        {self.js_player_ready}
        if (isNaN(player.duration)) return undefined;
        return player.currentTime;
        """
        webview.call_async_javascript_function(
            js, len(js),
            None,
            None,
            self.id,
            None,
            pass_me(self.on_js_double, webview, on_data)
        )
    def get_video_duration(self, webview, on_data):
        js = f"""
        {self.js_has_player}
        let players = document.getElementsByTagName('video');
        if (players.length != 1) return undefined;
        let player = players[0];
        {self.js_player_ready}
        if (isNaN(player.duration)) return undefined;
        return player.duration;
        """
        webview.call_async_javascript_function(
            js, len(js),
            None,
            None,
            self.id,
            None,
            pass_me(self.on_js_double, webview, on_data)
        )
    def set_video_playback_position(self, webview, timestamp, on_done):
        js = f"""
        {self.js_has_player}
        let players = document.getElementsByTagName('video');
        if (players.length != 1) return false;
        let player = players[0];
        {self.js_player_ready}
        if (timestamp > player.duration || timestamp < 0) return false;
        if (!player.seekable) return false;
        player.currentTime = timestamp;
        return true;
        """
        tab = GLib.VariantDict()
        tab.insert_value("timestamp", GLib.Variant("d", timestamp))
        args = tab.end()
        webview.call_async_javascript_function(
            js, len(js),
            args,
            None,
            self.id,
            None,
            pass_me(self.on_js_bool, webview, on_done)
        )
    def connect_video_ended(self, webview, on_done):
        js = f"""
        {self.js_has_player}
        let players = document.getElementsByTagName('video');
        if (players.length != 1) return undefined;
        let player = players[0];
        if (!player.ended)
            await new Promise((res)=>{{
                let list = player.addEventListener(
                    'ended',
                    ()=>{{
                        player.removeEventListener('ended', list);
                        res(true);
                    }}
                );
            }});

        return player.ended;
        """
        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
        try:
            val = webview.call_async_javascript_function_finish(async_res)
        except Exception as e:
            on_bool(False)
            return
        if val.is_boolean():
            on_bool(val.to_boolean())
        else:
            on_bool(False)
    def on_js_double(self, obj, async_res, webview, on_double):
        if on_double is None:
            return
        try:
            val = webview.call_async_javascript_function_finish(async_res)
        except Exception as e:
            on_double(None)
            return
        if val.is_number():
            on_double(val.to_double())
        else:
            on_double(None)