diff options
Diffstat (limited to 'src/client/players')
| -rw-r--r-- | src/client/players/Raw.hx | 169 | ||||
| -rw-r--r-- | src/client/players/Vk.hx | 2 | ||||
| -rw-r--r-- | src/client/players/Youtube.hx | 2 |
3 files changed, 138 insertions, 35 deletions
diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx index e3337ae..f51bd9e 100644 --- a/src/client/players/Raw.hx +++ b/src/client/players/Raw.hx @@ -13,6 +13,9 @@ import js.html.Element; import js.html.InputElement; import js.html.URL; import js.html.VideoElement; +import js.html.audio.AudioContext; +import js.html.audio.GainNode; +import js.lib.Uint8Array; class Raw implements IPlayer { final main:Main; @@ -24,7 +27,10 @@ class Raw implements IPlayer { var controlsHider:Timer; var playAllowed = true; var video:VideoElement; - var isHlsLoaded = false; + var isHlsPluginLoaded = false; + var hls:Hls; + var audioCtx:AudioContext; + var gainNode:GainNode; public function new(main:Main, player:Player) { this.main = main; @@ -39,21 +45,23 @@ class Raw implements IPlayer { return true; } + public function isHlsItem(url:String, title:String):Bool { + return url.contains("m3u8") || title.endsWith("m3u8"); + } + public function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void { final url = data.url; - final decodedUrl = url.urlDecode(); - final optTitle = titleInput.value.trim(); - var title = decodedUrl.substr(decodedUrl.lastIndexOf("/") + 1); - final isNameMatched = matchName.match(title); - if (optTitle != "") title = optTitle; - else if (isNameMatched) title = matchName.matched(1); - else title = Lang.get("rawVideo"); + var title = titleInput.value.trim(); + if (title.length == 0) { + final decodedUrl = url.urlDecode(); + final lastPart = decodedUrl.substr(decodedUrl.lastIndexOf("/") + 1); + if (matchName.match(lastPart)) title = matchName.matched(1); + else title = Lang.get("rawVideo"); + } - var isHls = false; - if (isNameMatched) isHls = matchName.matched(2).contains("m3u8"); - else isHls = title.endsWith("m3u8"); - if (isHls && !isHlsLoaded) { + final isHls = isHlsItem(url, title); + if (isHls && !isHlsPluginLoaded) { loadHlsPlugin(() -> getVideoData(data, callback)); return; } @@ -61,52 +69,88 @@ class Raw implements IPlayer { titleInput.value = ""; final subs = subsInput.value.trim(); subsInput.value = ""; + + getVideoDuration(url, isHls, duration -> { + if (duration == 0) { + callback({duration: duration}); + return; + } + callback({ + duration: duration, + title: title, + subs: subs, + }); + }); + } + + function getVideoDuration(url:String, isHls:Bool, callback:(duration:Float) -> + Void, isAnonCrossOrigin = false):Void { final video = document.createVideoElement(); - video.id = "temp-videoplayer"; + if (isAnonCrossOrigin) video.crossOrigin = "anonymous"; + video.className = "temp-videoplayer"; video.src = url; - video.onerror = e -> { + var tempHls:Hls = null; + inline function dispose():Void { if (playerEl.contains(video)) playerEl.removeChild(video); - callback({duration: 0}); + video.onerror = null; + video.onloadedmetadata = null; + tempHls?.destroy(); + video.pause(); + video.removeAttribute("src"); + video.load(); + } + video.onerror = e -> { + callback(0); + dispose(); } video.onloadedmetadata = () -> { - if (playerEl.contains(video)) playerEl.removeChild(video); - callback({ - duration: video.duration, - title: title, - subs: subs, + callback(video.duration); + dispose(); + } + if (isHls) { + tempHls = initHlsSource(video, url); + tempHls.on(Hls.Events.ERROR, (errorType, e) -> { + callback(0); + dispose(); }); } playerEl.prepend(video); - if (isHls) initHlsSource(video, url); } function loadHlsPlugin(callback:() -> Void):Void { final url = "https://cdn.jsdelivr.net/npm/hls.js@latest"; JsApi.addScriptToHead(url, () -> { - isHlsLoaded = true; + isHlsPluginLoaded = true; callback(); }); } - function initHlsSource(video:VideoElement, url:String):Void { - if (!Hls.isSupported()) return; - final hls = new Hls(); + function initHlsSource(video:VideoElement, url:String, ?hls:Hls):Null<Hls> { + if (!Hls.isSupported()) return null; + hls?.detachMedia(); + hls ??= new Hls(); hls.loadSource(url); hls.attachMedia(video); + return hls; } public function loadVideo(item:VideoItem):Void { final url = main.tryLocalIp(item.url); - final isHls = url.contains("m3u8") || item.title.endsWith("m3u8"); - if (isHls && !isHlsLoaded) { + final isHls = isHlsItem(url, item.title); + if (isHls && !isHlsPluginLoaded) { loadHlsPlugin(() -> loadVideo(item)); return; } + // we need to fully reset element if we had audio handling + if (audioCtx != null) removeVideo(); if (video != null) { + hls?.detachMedia(); video.src = url; - for (element in video.children) { - if (element.nodeName != "TRACK") continue; - element.remove(); + + var i = video.children.length; + while (i-- > 0) { + final child = video.children[i]; + if (child.nodeName == "TRACK") child.remove(); } } else { video = document.createVideoElement(); @@ -124,7 +168,7 @@ class Raw implements IPlayer { if (!main.isAutoplayAllowed()) video.muted = true; playerEl.appendChild(video); } - if (isHls) initHlsSource(video, url); + if (isHls) hls = initHlsSource(video, url, hls); restartControlsHider(); var subsUrl = item.subs ?? return; @@ -153,20 +197,79 @@ class Raw implements IPlayer { function restartControlsHider():Void { video.controls = true; if (Utils.isTouch()) return; - if (controlsHider != null) controlsHider.stop(); + controlsHider?.stop(); controlsHider = Timer.delay(() -> { if (video == null) return; video.controls = false; }, 3000); video.onmousemove = e -> { - if (controlsHider != null) controlsHider.stop(); + controlsHider?.stop(); video.controls = true; video.onmousemove = null; } } + public function boostVolume(volume:Float):Void { + if (gainNode != null) { + gainNode.gain.value = volume; + return; + } + if (volume <= 1) return; + if (video.crossOrigin != "anonymous") { + final item = player.getCurrentItem() ?? return; + final isHls = isHlsItem(item.url, item.title); + getVideoDuration(item.url, isHls, duration -> { + if (duration == 0) { + main.serverMessage("Cannot boost volume for this video, no CORS access."); + } else { + video.crossOrigin = "anonymous"; + boostVolume(volume); + } + }, true); + return; + } + audioCtx ??= Utils.createAudioContext() ?? return; + final sourceNode = audioCtx.createMediaElementSource(video); + gainNode = audioCtx.createGain(); + gainNode.gain.value = volume; + sourceNode.connect(gainNode); + gainNode.connect(audioCtx.destination); + + // we need silence check if audio context is too picky about cors + final analyzer = audioCtx.createAnalyser(); + final bufferSize = 256; + analyzer.fftSize = bufferSize; + sourceNode.connect(analyzer); + final arrayBuffer = new Uint8Array(bufferSize); + inline function isSilence():Bool { + analyzer.getByteFrequencyData(arrayBuffer); + var sum = 0; + for (i in arrayBuffer) sum += i; + return sum == 0; + } + // src refresh should be enough since video with + // crossOrigin="anonymous" loads finely + Timer.delay(() -> { + if (isSilence()) { + final item = player.getCurrentItem() ?? return; + video.src = item.url; + final isHls = isHlsItem(item.url, item.title); + initHlsSource(video, item.url, hls); + } + }, 300); + } + + function destroyAudioContext():Void { + if (audioCtx == null) return; + gainNode = null; + audioCtx.close(); + audioCtx = null; + } + public function removeVideo():Void { if (video == null) return; + destroyAudioContext(); + hls?.detachMedia(); video.pause(); video.removeAttribute("src"); video.load(); diff --git a/src/client/players/Vk.hx b/src/client/players/Vk.hx index 99643de..f1976ca 100644 --- a/src/client/players/Vk.hx +++ b/src/client/players/Vk.hx @@ -102,7 +102,7 @@ class Vk implements IPlayer { final oid = ids.oid; final id = ids.id; final tempVideo = Utils.nodeFromString( - '<iframe id="temp-videoplayer" src="https://vk.com/video_ext.php?oid=$oid&id=$id&hd=1&js_api=1" + '<iframe class="temp-videoplayer" src="https://vk.com/video_ext.php?oid=$oid&id=$id&hd=1&js_api=1" allow="autoplay; encrypted-media; fullscreen; picture-in-picture;" frameborder="0" allowfullscreen> </iframe>'.trim() diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx index 7c851fb..6865394 100644 --- a/src/client/players/Youtube.hx +++ b/src/client/players/Youtube.hx @@ -174,7 +174,7 @@ class Youtube implements IPlayer { return; } final video = document.createDivElement(); - video.id = "temp-videoplayer"; + video.className = "temp-videoplayer"; playerEl.prepend(video); var tempYoutube:YoutubePlayer = null; tempYoutube = new YoutubePlayer(video.id, { |
