aboutsummaryrefslogtreecommitdiffstats
path: root/src/client/players
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/players')
-rw-r--r--src/client/players/Raw.hx169
-rw-r--r--src/client/players/Vk.hx2
-rw-r--r--src/client/players/Youtube.hx2
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, {
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage