@@ 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; }
@@ 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)