aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Types.hx3
-rw-r--r--src/client/Buttons.hx1
-rw-r--r--src/client/IPlayer.hx4
-rw-r--r--src/client/Main.hx29
-rw-r--r--src/client/Player.hx124
-rw-r--r--src/client/players/Iframe.hx15
-rw-r--r--src/client/players/Raw.hx19
-rw-r--r--src/client/players/Youtube.hx26
-rw-r--r--src/server/Main.hx2
9 files changed, 210 insertions, 13 deletions
diff --git a/src/Types.hx b/src/Types.hx
index 0e89b85..8567f7b 100644
--- a/src/Types.hx
+++ b/src/Types.hx
@@ -13,6 +13,7 @@ typedef VideoData = {
var ?title:String;
var ?url:String;
var ?subs:String;
+ var ?voiceOverTrack:String;
var ?isIframe:Bool;
}
@@ -106,6 +107,7 @@ typedef VideoItem = {
var author:String;
var duration:Float;
var ?subs:String;
+ var ?voiceOverTrack:String;
var isTemp:Bool;
var isIframe:Bool;
}
@@ -118,6 +120,7 @@ private class VideoItemTools {
author: item.author,
duration: item.duration,
subs: item.subs,
+ voiceOverTrack: item.voiceOverTrack,
isTemp: item.isTemp,
isIframe: item.isIframe
};
diff --git a/src/client/Buttons.hx b/src/client/Buttons.hx
index de3aa27..c3581e2 100644
--- a/src/client/Buttons.hx
+++ b/src/client/Buttons.hx
@@ -218,6 +218,7 @@ class Buttons {
&& main.isSingleVideoLink(value);
ge("#mediatitleblock").style.display = isRawSingleVideo ? "" : "none";
ge("#subsurlblock").style.display = isRawSingleVideo ? "" : "none";
+ ge("#voiceoverblock").style.display = value.length > 0 ? "" : "none";
final panel = ge("#addfromurl");
final oldH = panel.style.height; // save for animation
panel.style.height = ""; // to calculate height from content
diff --git a/src/client/IPlayer.hx b/src/client/IPlayer.hx
index 903902e..032ac97 100644
--- a/src/client/IPlayer.hx
+++ b/src/client/IPlayer.hx
@@ -12,8 +12,12 @@ interface IPlayer {
function isVideoLoaded():Bool;
function play():Void;
function pause():Void;
+ function isPaused():Bool;
function getTime():Float;
function setTime(time:Float):Void;
function getPlaybackRate():Float;
function setPlaybackRate(rate:Float):Void;
+ function getVolume():Float;
+ function setVolume(volume:Float):Void;
+ function unmute():Void;
}
diff --git a/src/client/Main.hx b/src/client/Main.hx
index 2850c75..248e205 100644
--- a/src/client/Main.hx
+++ b/src/client/Main.hx
@@ -48,6 +48,7 @@ class Main {
final player:Player;
var onTimeGet:Timer;
var onBlinkTab:Null<Timer>;
+ var gotFirstPageInteraction = false;
static function main():Void {
new Main();
@@ -94,6 +95,17 @@ class Main {
openWebSocket();
});
JsApi.init(this, player);
+
+ document.addEventListener("click", onFirstInteraction);
+ }
+
+ function onFirstInteraction():Void {
+ if (gotFirstPageInteraction) return;
+ if (!player.isVideoLoaded()) return;
+ gotFirstPageInteraction = true;
+ player.unmute();
+ player.play();
+ document.removeEventListener("click", onFirstInteraction);
}
function settingsPatcher(data:Any, version:Int):Any {
@@ -319,6 +331,7 @@ class Main {
duration: data.duration,
isTemp: isTemp,
subs: data.subs,
+ voiceOverTrack: data.voiceOverTrack,
isIframe: data.isIframe == true
},
atEnd: atEnd
@@ -526,8 +539,11 @@ class Main {
}
if (player.isVideoLoaded()) forceSyncNextTick = false;
if (player.getDuration() <= player.getTime() + synchThreshold) return;
- if (!data.getTime.paused) player.play();
- else player.pause();
+ if (player.isPaused()) {
+ if (!data.getTime.paused) player.play();
+ } else {
+ if (data.getTime.paused) player.pause();
+ }
player.setPauseIndicator(!data.getTime.paused);
if (Math.abs(time - newTime) < synchThreshold) return;
// +0.5s for buffering
@@ -1199,6 +1215,15 @@ class Main {
return config.youtubePlaylistLimit;
}
+ public function isAutoplayAllowed():Bool {
+ final navigator:{
+ getAutoplayPolicy:(type:String) -> Bool
+ } = cast Browser.navigator;
+ if (navigator.getAutoplayPolicy != null) return
+ navigator.getAutoplayPolicy("mediaelement");
+ return gotFirstPageInteraction;
+ }
+
public function isVerbose():Bool {
return config.isVerbose;
}
diff --git a/src/client/Player.hx b/src/client/Player.hx
index bc64053..f40c34c 100644
--- a/src/client/Player.hx
+++ b/src/client/Player.hx
@@ -10,7 +10,9 @@ import client.players.Streamable;
import client.players.Youtube;
import haxe.Http;
import haxe.Json;
+import js.html.Audio;
import js.html.Element;
+import js.html.InputElement;
class Player {
final main:Main;
@@ -25,13 +27,21 @@ class Player {
var isLoaded = false;
var skipSetTime = false;
var skipSetRate = false;
+ var streamable:Streamable;
+
+ final voiceOverInput:InputElement = cast ge("#voiceoverurl");
+ var audioTrack:Null<Audio>;
+ var isAudioTrackLoaded = false;
+ var needsVolumeReset = false;
+ final voiceOverVolume = 0.3;
public function new(main:Main):Void {
this.main = main;
youtube = new Youtube(main, this);
+ streamable = new Streamable(main, this);
players = [
youtube,
- new Streamable(main, this)
+ streamable
];
iframePlayer = new Iframe(main, this);
rawPlayer = new Raw(main, this);
@@ -97,19 +107,26 @@ class Player {
if (player != null) {
JsApi.fireVideoRemoveEvents(videoList.currentItem);
player.removeVideo();
+ removeExternalAudioTrack();
}
main.blinkTabWithTitle("*Video*");
}
player = newPlayer;
}
- public function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void {
- var player = players.find(player -> player.isSupportedLink(data.url));
+ public function getVideoData(req:VideoDataRequest, callback:(data:VideoData) -> Void):Void {
+ var player = players.find(player -> player.isSupportedLink(req.url));
player ??= rawPlayer;
- player.getVideoData(data, callback);
+ player.getVideoData(req, data -> {
+ final voiceOverTrack = voiceOverInput.value.trim();
+ data.voiceOverTrack = voiceOverTrack;
+ voiceOverInput.value = "";
+ callback(data);
+ });
}
public function isRawPlayerLink(url:String):Bool {
+ if (streamable.isSupportedLink(url)) return true;
return !players.exists(player -> player.isSupportedLink(url));
}
@@ -129,6 +146,7 @@ class Player {
isLoaded = false;
if (main.isVideoEnabled) {
player.loadVideo(item);
+ setExternalAudioTrack(item);
} else {
onCanBePlayed();
}
@@ -136,6 +154,42 @@ class Player {
ge("#currenttitle").textContent = item.title;
}
+ function setExternalAudioTrack(item:VideoItem):Void {
+ removeExternalAudioTrack();
+ final voiceOverTrack = item.voiceOverTrack ?? return;
+ if (voiceOverTrack.length == 0) return;
+ audioTrack = new Audio(voiceOverTrack);
+ if (!main.isAutoplayAllowed()) {
+ audioTrack.muted = true;
+ }
+ inline function cleanAudioEvents() {
+ audioTrack.oncanplay = null;
+ audioTrack.onerror = null;
+ }
+ audioTrack.oncanplay = () -> {
+ cleanAudioEvents();
+ isAudioTrackLoaded = true;
+ }
+ audioTrack.onerror = e -> {
+ trace(e);
+ cleanAudioEvents();
+ isAudioTrackLoaded = false;
+ audioTrack = null;
+ setVolume(1);
+ }
+ }
+
+ function removeExternalAudioTrack():Void {
+ isAudioTrackLoaded = false;
+ needsVolumeReset = false;
+ if (audioTrack == null) return;
+
+ audioTrack?.pause();
+ audioTrack.src = null;
+ audioTrack = null;
+ needsVolumeReset = true;
+ }
+
function setSupportedPlayer(url:String, isIframe:Bool):Void {
final currentPlayer = players.find(p -> p.isSupportedLink(url));
if (currentPlayer != null) setPlayer(currentPlayer);
@@ -171,6 +225,8 @@ class Player {
}
public function onPlay():Void {
+ audioTrack?.play();
+
if (!main.isLeader()) return;
main.send({
type: Play,
@@ -186,6 +242,8 @@ class Player {
}
public function onPause():Void {
+ audioTrack?.pause();
+
final item = videoList.currentItem ?? return;
// do not send pause if video is ended
if (getTime() >= item.duration - 0.01) return;
@@ -193,8 +251,10 @@ class Player {
if (player == rawPlayer && youtube.isSupportedLink(item.url)) {
if (getTime() >= item.duration - 1) return;
}
- final hasAutoPause = main.hasLeaderOnPauseRequest() && videoList.length > 0
- && getTime() > 1;
+ final hasAutoPause = main.hasLeaderOnPauseRequest()
+ && videoList.length > 0
+ && getTime() > 1
+ && isLoaded;
if (hasAutoPause && !main.hasLeader()) {
JsApi.once(SetLeader, event -> {
final name = event.setLeader.clientName;
@@ -220,6 +280,10 @@ class Player {
}
public function onSetTime():Void {
+ if (audioTrack != null) {
+ audioTrack.currentTime = getTime();
+ }
+
if (skipSetTime) {
skipSetTime = false;
return;
@@ -234,6 +298,9 @@ class Player {
}
public function onRateChange():Void {
+ if (audioTrack != null) {
+ audioTrack.playbackRate = getPlaybackRate();
+ }
if (skipSetRate) {
skipSetRate = false;
return;
@@ -410,6 +477,7 @@ class Player {
}
public function isVideoLoaded():Bool {
+ if (player == null) return false;
return player.isVideoLoaded();
}
@@ -418,6 +486,12 @@ class Player {
if (player == null) return;
if (!player.isVideoLoaded()) return;
player.play();
+ if (needsVolumeReset) setVolume(1);
+
+ if (audioTrack != null) {
+ setVolume(0.3);
+ audioTrack?.play();
+ }
}
public function pause():Void {
@@ -425,6 +499,8 @@ class Player {
if (player == null) return;
if (!player.isVideoLoaded()) return;
player.pause();
+
+ audioTrack?.pause();
}
public function getTime():Float {
@@ -439,6 +515,8 @@ class Player {
if (!player.isVideoLoaded()) return;
skipSetTime = isLocal;
player.setTime(time);
+
+ if (audioTrack != null) audioTrack.currentTime = time;
}
public function getPlaybackRate():Float {
@@ -453,6 +531,8 @@ class Player {
if (!player.isVideoLoaded()) return;
skipSetRate = isLocal;
player.setPlaybackRate(rate);
+
+ if (audioTrack != null) audioTrack.playbackRate = rate;
}
public function skipAd():Void {
@@ -484,4 +564,36 @@ class Player {
http.onError = msg -> trace(msg);
http.request();
}
+
+ public function isPaused():Bool {
+ if (player == null) return true;
+ if (!player.isVideoLoaded()) return true;
+ return player.isPaused();
+ }
+
+ public function getVolume():Float {
+ if (player == null) return 1;
+ if (!player.isVideoLoaded()) return 1;
+ return player.getVolume();
+ }
+
+ public function setVolume(volume:Float):Void {
+ if (player == null) return;
+ if (!player.isVideoLoaded()) return;
+ player.setVolume(volume);
+ }
+
+ public function unmute():Void {
+ if (player == null) return;
+ if (!player.isVideoLoaded()) return;
+ player.unmute();
+ if (audioTrack != null) audioTrack.muted = false;
+ if (audioTrack == null && almostEq(getVolume(), voiceOverVolume, 0.01)) {
+ setVolume(1);
+ }
+ }
+
+ function almostEq(a:Float, b:Float, diff:Float):Bool {
+ return a > b - diff && a < b + diff;
+ }
}
diff --git a/src/client/players/Iframe.hx b/src/client/players/Iframe.hx
index e07f814..56cf319 100644
--- a/src/client/players/Iframe.hx
+++ b/src/client/players/Iframe.hx
@@ -34,7 +34,8 @@ class Iframe implements IPlayer {
function isValidIframe(iframe:Element):Bool {
if (iframe.children.length != 1) return false;
- return (iframe.firstChild.nodeName == "IFRAME" || iframe.firstChild.nodeName == "OBJECT");
+ return (iframe.firstChild.nodeName == "IFRAME"
+ || iframe.firstChild.nodeName == "OBJECT");
}
public function loadVideo(item:VideoItem):Void {
@@ -66,6 +67,10 @@ class Iframe implements IPlayer {
public function pause():Void {}
+ public function isPaused():Bool {
+ return false;
+ }
+
public function getTime():Float {
return 0;
}
@@ -77,4 +82,12 @@ class Iframe implements IPlayer {
}
public function setPlaybackRate(rate:Float):Void {}
+
+ public function getVolume():Float {
+ return 1;
+ }
+
+ public function setVolume(volume:Float) {}
+
+ public function unmute():Void {}
}
diff --git a/src/client/players/Raw.hx b/src/client/players/Raw.hx
index 3f69958..5c5b7a4 100644
--- a/src/client/players/Raw.hx
+++ b/src/client/players/Raw.hx
@@ -67,7 +67,7 @@ class Raw implements IPlayer {
callback({
duration: video.duration,
title: title,
- subs: subs
+ subs: subs,
});
}
Utils.prepend(playerEl, video);
@@ -115,6 +115,7 @@ class Raw implements IPlayer {
}
video.onpause = player.onPause;
video.onratechange = player.onRateChange;
+ if (!main.isAutoplayAllowed()) video.muted = true;
playerEl.appendChild(video);
}
if (isHls) initHlsSource(video, url);
@@ -185,6 +186,10 @@ class Raw implements IPlayer {
video.pause();
}
+ public function isPaused():Bool {
+ return video.paused;
+ }
+
public function getTime():Float {
return video.currentTime;
}
@@ -200,4 +205,16 @@ class Raw implements IPlayer {
public function setPlaybackRate(rate:Float):Void {
video.playbackRate = rate;
}
+
+ public function getVolume():Float {
+ return video.volume;
+ }
+
+ public function setVolume(volume:Float):Void {
+ video.volume = volume;
+ }
+
+ public function unmute():Void {
+ video.muted = false;
+ }
}
diff --git a/src/client/players/Youtube.hx b/src/client/players/Youtube.hx
index cde2ef8..3ad9030 100644
--- a/src/client/players/Youtube.hx
+++ b/src/client/players/Youtube.hx
@@ -86,10 +86,11 @@ class Youtube implements IPlayer {
final duration = convertTime(duration);
// duration is PT0S for streams
if (duration == 0) {
+ final mute = main.isAutoplayAllowed() ? "" : "&mute=1";
callback({
duration: 99 * 60 * 60,
title: title,
- url: '<iframe src="https://www.youtube.com/embed/$id" frameborder="0"
+ url: '<iframe src="https://www.youtube.com/embed/$id?autoplay=1$mute" frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>',
isIframe: true
@@ -211,13 +212,14 @@ class Youtube implements IPlayer {
videoId: extractVideoId(item.url),
playerVars: {
autoplay: 1,
+ // play videos inline instead of fullscreen on iOS
playsinline: 1,
- modestbranding: 1,
+ // related videos only from same channel
rel: 0,
- showinfo: 0
},
events: {
onReady: e -> {
+ if (!main.isAutoplayAllowed()) e.target.mute();
isLoaded = true;
youtube.pauseVideo();
},
@@ -271,6 +273,7 @@ class Youtube implements IPlayer {
info.formats ??= [];
info.adaptiveFormats ??= [];
final formats = info.adaptiveFormats.concat(info.formats);
+ trace(formats);
final qPriority = [1080, 720, 480, 360, 240];
for (q in qPriority) {
final quality = '${q}p';
@@ -304,6 +307,10 @@ class Youtube implements IPlayer {
youtube.pauseVideo();
}
+ public function isPaused():Bool {
+ return youtube.getPlayerState() == PAUSED;
+ }
+
public function getTime():Float {
return youtube.getCurrentTime();
}
@@ -319,4 +326,17 @@ class Youtube implements IPlayer {
public function setPlaybackRate(rate:Float):Void {
youtube.setPlaybackRate(rate);
}
+
+ public function getVolume():Float {
+ if (youtube.isMuted()) return 0;
+ return youtube.getVolume() / 100;
+ }
+
+ public function setVolume(volume:Float):Void {
+ youtube.setVolume(Std.int(volume * 100));
+ }
+
+ public function unmute():Void {
+ youtube.unMute();
+ }
}
diff --git a/src/server/Main.hx b/src/server/Main.hx
index a0b255e..9c2f6e2 100644
--- a/src/server/Main.hx
+++ b/src/server/Main.hx
@@ -139,6 +139,8 @@ class Main {
trace("Global network is disabled in config");
} else {
if (!isNoState) Utils.getGlobalIp(ip -> {
+ final isIp6 = ip.contains(":");
+ if (isIp6) ip = '[$ip]';
globalIp = ip;
trace('Global: http://$globalIp:$port');
});
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage