aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRblSb <msrblsb@gmail.com>2025-01-17 04:11:46 +0300
committerRblSb <msrblsb@gmail.com>2025-01-17 04:11:46 +0300
commit600101bb1d093c2f0402ddf38a407f140c4329ed (patch)
treee52bc4d6ab36da535cc94d543ac666f342bc51cb
parentd9ca7beaa9494cf34590853901cf8be44e243775 (diff)
VK player support
-rw-r--r--README.md1
-rw-r--r--build/server.js2
-rw-r--r--res/client.js170
-rw-r--r--src/Types.hx1
-rw-r--r--src/client/Player.hx2
-rw-r--r--src/client/players/Vk.hx228
6 files changed, 401 insertions, 3 deletions
diff --git a/README.md b/README.md
index f3f9725..09a3a47 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ Default channel example: https://synctube.onrender.com/
### Supported players
- Youtube (videos, shorts, streams and playlists)
- [Streamable](https://streamable.com)
+- [VK](https://vk.com/video)
- Raw mp4 videos and m3u8 playlists (or any other media format supported in browser)
- Iframes (without sync)
diff --git a/build/server.js b/build/server.js
index 12effdf..6c00510 100644
--- a/build/server.js
+++ b/build/server.js
@@ -1399,7 +1399,7 @@ JsonParser_$45.prototype = $extend(json2object_reader_BaseParser.prototype,{
this.value = null;
}
,loadJsonString: function(s,pos,variable) {
- this.value = this.loadString(s,pos,variable,["RawType","YoutubeType","IframeType"],"RawType");
+ this.value = this.loadString(s,pos,variable,["RawType","YoutubeType","VkType","IframeType"],"RawType");
}
,__class__: JsonParser_$45
});
diff --git a/res/client.js b/res/client.js
index 338ed34..efcd05c 100644
--- a/res/client.js
+++ b/res/client.js
@@ -2453,7 +2453,7 @@ var client_Player = function(main) {
this.main = main;
this.youtube = new client_players_Youtube(main,this);
this.streamable = new client_players_Streamable(main,this);
- this.players = [this.youtube,this.streamable];
+ this.players = [this.youtube,new client_players_Vk(main,this),this.streamable];
this.iframePlayer = new client_players_Iframe(main,this);
this.rawPlayer = new client_players_Raw(main,this);
this.initItemButtons();
@@ -2587,7 +2587,7 @@ client_Player.prototype = {
return _gthis.isAudioTrackLoaded = true;
};
this.audioTrack.onerror = function(e) {
- haxe_Log.trace(e,{ fileName : "src/client/Player.hx", lineNumber : 189, className : "client.Player", methodName : "setExternalAudioTrack"});
+ haxe_Log.trace(e,{ fileName : "src/client/Player.hx", lineNumber : 191, className : "client.Player", methodName : "setExternalAudioTrack"});
_gthis.audioTrack.oncanplay = null;
_gthis.audioTrack.onerror = null;
_gthis.isAudioTrackLoaded = false;
@@ -3859,6 +3859,172 @@ client_players_Streamable.prototype = $extend(client_players_Raw.prototype,{
http.request();
}
});
+var client_players_Vk = function(main,player) {
+ this.matchIds = new EReg("video(-?[0-9]+)_([0-9]+)","g");
+ this.matchVk = new EReg("(vk.com/video|vkvideo)","g");
+ this.isApiLoaded = false;
+ this.isLoaded = false;
+ this.playerEl = window.document.querySelector("#ytapiplayer");
+ this.main = main;
+ this.player = player;
+};
+client_players_Vk.__name__ = true;
+client_players_Vk.prototype = {
+ getPlayerType: function() {
+ return "VkType";
+ }
+ ,isSupportedLink: function(url) {
+ if(this.matchVk.match(url)) {
+ return this.getVideoIds(url) != null;
+ } else {
+ return false;
+ }
+ }
+ ,getVideoIds: function(url) {
+ if(!this.matchIds.match(url)) {
+ haxe_Log.trace("Cannot extract /video-oid_id values from url:",{ fileName : "src/client/players/Vk.hx", lineNumber : 68, className : "client.players.Vk", methodName : "getVideoIds"});
+ return null;
+ }
+ return { oid : this.matchIds.matched(1), id : this.matchIds.matched(2)};
+ }
+ ,loadApi: function(callback) {
+ var _gthis = this;
+ client_JsApi.addScriptToHead("https://vk.com/js/api/videoplayer.js",function() {
+ _gthis.isApiLoaded = true;
+ callback();
+ });
+ }
+ ,createVkPlayer: function(iframe) {
+ return VK.VideoPlayer(iframe);
+ }
+ ,getVideoData: function(data,callback) {
+ var _gthis = this;
+ if(!this.isApiLoaded) {
+ this.loadApi(function() {
+ _gthis.getVideoData(data,callback);
+ });
+ return;
+ }
+ var url = data.url;
+ var video = window.document.createElement("div");
+ video.id = "temp-videoplayer";
+ var ids = this.getVideoIds(url);
+ if(ids == null) {
+ callback({ duration : 0});
+ return;
+ }
+ video.innerHTML = StringTools.trim("\n\t\t\t<iframe src=\"https://vk.com/video_ext.php?oid=" + ids.oid + "&id=" + ids.id + "&hd=1&js_api=1\"\n\t\t\t\tallow=\"autoplay; encrypted-media; fullscreen; picture-in-picture;\"\n\t\t\t\tframeborder=\"0\" allowfullscreen>\n\t\t\t</iframe>\n\t\t");
+ client_Utils.prepend(this.playerEl,video);
+ var tempVkPlayer = this.createVkPlayer(video.firstChild);
+ tempVkPlayer.on("inited",function() {
+ callback({ duration : tempVkPlayer.getDuration(), title : "VK media", url : url});
+ tempVkPlayer.destroy();
+ if(_gthis.playerEl.contains(video)) {
+ _gthis.playerEl.removeChild(video);
+ }
+ });
+ }
+ ,loadVideo: function(item) {
+ var _gthis = this;
+ if(!this.isApiLoaded) {
+ this.loadApi(function() {
+ _gthis.loadVideo(item);
+ });
+ return;
+ }
+ this.removeVideo();
+ var tmp = this.getVideoIds(item.url);
+ if(tmp == null) {
+ return;
+ }
+ this.video = window.document.createElement("div");
+ this.video.id = "videoplayer";
+ this.video.innerHTML = StringTools.trim("\n\t\t\t<iframe src=\"https://vk.com/video_ext.php?oid=" + tmp.oid + "&id=" + tmp.id + "&hd=4&js_api=1\"\n\t\t\t\tallow=\"autoplay; encrypted-media; fullscreen; picture-in-picture;\"\n\t\t\t\tframeborder=\"0\" allowfullscreen>\n\t\t\t</iframe>\n\t\t");
+ this.playerEl.appendChild(this.video);
+ this.vkPlayer = this.createVkPlayer(this.video.firstChild);
+ this.vkPlayer.on("inited",function() {
+ if(!_gthis.main.isAutoplayAllowed()) {
+ _gthis.vkPlayer.mute();
+ }
+ _gthis.isLoaded = true;
+ _gthis.vkPlayer.pause();
+ _gthis.setTime(0);
+ _gthis.player.onCanBePlayed();
+ });
+ this.vkPlayer.on("started",function() {
+ _gthis.player.onPlay();
+ });
+ this.vkPlayer.on("resumed",function() {
+ _gthis.player.onPlay();
+ });
+ this.vkPlayer.on("paused",function() {
+ _gthis.player.onPause();
+ });
+ this.vkPlayer.on("error",function(e) {
+ haxe_Log.trace("Error " + e,{ fileName : "src/client/players/Vk.hx", lineNumber : 166, className : "client.players.Vk", methodName : "loadVideo"});
+ });
+ var prevTime = 0.0;
+ this.vkPlayer.on("timeupdate",function(e) {
+ var diff = Math.abs(prevTime - e.time);
+ prevTime = e.time;
+ if(diff > 1) {
+ _gthis.player.onSetTime();
+ }
+ });
+ }
+ ,removeVideo: function() {
+ if(this.video == null) {
+ return;
+ }
+ this.isLoaded = false;
+ this.vkPlayer.destroy();
+ this.vkPlayer = null;
+ if(this.playerEl.contains(this.video)) {
+ this.playerEl.removeChild(this.video);
+ }
+ this.video = null;
+ }
+ ,isVideoLoaded: function() {
+ return this.isLoaded;
+ }
+ ,play: function() {
+ this.vkPlayer.play();
+ }
+ ,pause: function() {
+ this.vkPlayer.pause();
+ }
+ ,isPaused: function() {
+ var state = this.vkPlayer.getState();
+ if(state != "unstarted") {
+ return state == "paused";
+ } else {
+ return true;
+ }
+ }
+ ,getTime: function() {
+ return this.vkPlayer.getCurrentTime();
+ }
+ ,setTime: function(time) {
+ this.vkPlayer.seek(time);
+ }
+ ,getPlaybackRate: function() {
+ return 1;
+ }
+ ,setPlaybackRate: function(rate) {
+ }
+ ,getVolume: function() {
+ if(this.vkPlayer.isMuted()) {
+ return 0;
+ }
+ return this.vkPlayer.getVolume();
+ }
+ ,setVolume: function(volume) {
+ this.vkPlayer.setVolume(volume);
+ }
+ ,unmute: function() {
+ this.vkPlayer.unmute();
+ }
+};
var client_players_Youtube = function(main,player) {
this.matchSeconds = new EReg("([0-9]+)S","");
this.matchMinutes = new EReg("([0-9]+)M","");
diff --git a/src/Types.hx b/src/Types.hx
index 0f56392..3d25fda 100644
--- a/src/Types.hx
+++ b/src/Types.hx
@@ -5,6 +5,7 @@ import Client.ClientData;
enum abstract PlayerType(String) {
var RawType;
var YoutubeType;
+ var VkType;
var IframeType;
}
diff --git a/src/client/Player.hx b/src/client/Player.hx
index 70efcf9..f51d017 100644
--- a/src/client/Player.hx
+++ b/src/client/Player.hx
@@ -8,6 +8,7 @@ import client.Main.ge;
import client.players.Iframe;
import client.players.Raw;
import client.players.Streamable;
+import client.players.Vk;
import client.players.Youtube;
import haxe.Http;
import haxe.Json;
@@ -42,6 +43,7 @@ class Player {
streamable = new Streamable(main, this);
players = [
youtube,
+ new Vk(main, this),
streamable,
];
iframePlayer = new Iframe(main, this);
diff --git a/src/client/players/Vk.hx b/src/client/players/Vk.hx
new file mode 100644
index 0000000..a2af220
--- /dev/null
+++ b/src/client/players/Vk.hx
@@ -0,0 +1,228 @@
+package client.players;
+
+import Types.PlayerType;
+import Types.VideoData;
+import Types.VideoDataRequest;
+import Types.VideoItem;
+import client.Main.ge;
+import haxe.Constraints.Function;
+import js.Browser.document;
+import js.html.Element;
+import js.html.Node;
+
+private enum abstract VkPlayerState(String) {
+ var Uninited = "uninited";
+ var Unstarted = "unstarted";
+ var Playing = "playing";
+ var Paused = "paused";
+ var Ended = "ended";
+ var Error = "error";
+}
+
+private extern class VkPlayer {
+ function play():Void;
+ function pause():Void;
+ function seek(time:Float):Void;
+ function seekLive():Void;
+ function setVolume(volume:Float):Void;
+ function getVolume():Float;
+ function getCurrentTime():Float;
+ function getDuration():Int;
+ function getQuality():Int; // 480, etc
+ function mute():Void;
+ function unmute():Void;
+ function isMuted():Bool;
+ function getState():VkPlayerState;
+ function on(event:String, listener:Function):Void;
+ function off(event:String, listener:Function):Void;
+ function destroy():Void;
+}
+
+class Vk implements IPlayer {
+ final main:Main;
+ final player:Player;
+ final playerEl:Element = ge("#ytapiplayer");
+ var video:Element;
+ var vkPlayer:VkPlayer;
+ var isLoaded = false;
+ var isApiLoaded = false;
+
+ public function new(main:Main, player:Player) {
+ this.main = main;
+ this.player = player;
+ }
+
+ public function getPlayerType():PlayerType {
+ return VkType;
+ }
+
+ final matchVk = ~/(vk.com\/video|vkvideo)/g;
+ final matchIds = ~/video(-?[0-9]+)_([0-9]+)/g;
+
+ public function isSupportedLink(url:String):Bool {
+ return matchVk.match(url) && getVideoIds(url) != null;
+ }
+
+ function getVideoIds(url:String):Null<{oid:String, id:String}> {
+ if (!matchIds.match(url)) {
+ trace("Cannot extract /video-oid_id values from url:");
+ return null;
+ }
+ final oid = matchIds.matched(1);
+ final id = matchIds.matched(2);
+ return {oid: oid, id: id};
+ }
+
+ function loadApi(callback:() -> Void):Void {
+ final url = "https://vk.com/js/api/videoplayer.js";
+ JsApi.addScriptToHead(url, () -> {
+ isApiLoaded = true;
+ callback();
+ });
+ }
+
+ function createVkPlayer(iframe:Node):VkPlayer {
+ return untyped VK.VideoPlayer(iframe);
+ }
+
+ public function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void):Void {
+ if (!isApiLoaded) {
+ loadApi(() -> {
+ getVideoData(data, callback);
+ });
+ return;
+ }
+ final url = data.url;
+
+ final video = document.createDivElement();
+ video.id = "temp-videoplayer";
+ final ids = getVideoIds(url);
+ if (ids == null) {
+ callback({duration: 0});
+ return;
+ }
+ final oid = ids.oid;
+ final id = ids.id;
+ video.innerHTML = '
+ <iframe 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();
+ Utils.prepend(playerEl, video);
+ final tempVkPlayer = createVkPlayer(video.firstChild);
+ tempVkPlayer.on("inited", () -> {
+ callback({
+ duration: tempVkPlayer.getDuration(),
+ title: "VK media",
+ url: url
+ });
+ tempVkPlayer.destroy();
+ if (playerEl.contains(video)) playerEl.removeChild(video);
+ });
+ }
+
+ public function loadVideo(item:VideoItem):Void {
+ if (!isApiLoaded) {
+ loadApi(() -> {
+ loadVideo(item);
+ });
+ return;
+ }
+
+ removeVideo();
+
+ final ids = getVideoIds(item.url) ?? return;
+ video = document.createDivElement();
+ video.id = "videoplayer";
+ final oid = ids.oid;
+ final id = ids.id;
+ video.innerHTML = '
+ <iframe src="https://vk.com/video_ext.php?oid=$oid&id=$id&hd=4&js_api=1"
+ allow="autoplay; encrypted-media; fullscreen; picture-in-picture;"
+ frameborder="0" allowfullscreen>
+ </iframe>
+ '.trim();
+ playerEl.appendChild(video);
+ vkPlayer = createVkPlayer(video.firstChild);
+ vkPlayer.on("inited", () -> {
+ if (!main.isAutoplayAllowed()) vkPlayer.mute();
+ isLoaded = true;
+ vkPlayer.pause();
+ setTime(0);
+ player.onCanBePlayed();
+ });
+
+ vkPlayer.on("started", () -> {
+ player.onPlay();
+ });
+ vkPlayer.on("resumed", () -> {
+ player.onPlay();
+ });
+ vkPlayer.on("paused", () -> {
+ player.onPause();
+ });
+ vkPlayer.on("error", e -> {
+ trace('Error $e');
+ });
+ var prevTime = 0.0;
+ vkPlayer.on("timeupdate", (e:{time:Float}) -> {
+ final diff = Math.abs(prevTime - e.time);
+ prevTime = e.time;
+ if (diff > 1) player.onSetTime();
+ });
+ }
+
+ public function removeVideo():Void {
+ if (video == null) return;
+ isLoaded = false;
+ vkPlayer.destroy();
+ vkPlayer = null;
+ if (playerEl.contains(video)) playerEl.removeChild(video);
+ video = null;
+ }
+
+ public function isVideoLoaded():Bool {
+ return isLoaded;
+ }
+
+ public function play():Void {
+ vkPlayer.play();
+ }
+
+ public function pause():Void {
+ vkPlayer.pause();
+ }
+
+ public function isPaused():Bool {
+ final state = vkPlayer.getState();
+ return state == Unstarted || state == Paused;
+ }
+
+ public function getTime():Float {
+ return vkPlayer.getCurrentTime();
+ }
+
+ public function setTime(time:Float):Void {
+ vkPlayer.seek(time);
+ }
+
+ public function getPlaybackRate():Float {
+ return 1;
+ }
+
+ public function setPlaybackRate(rate:Float):Void {}
+
+ public function getVolume():Float {
+ if (vkPlayer.isMuted()) return 0;
+ return vkPlayer.getVolume();
+ }
+
+ public function setVolume(volume:Float):Void {
+ vkPlayer.setVolume(volume);
+ }
+
+ public function unmute():Void {
+ vkPlayer.unmute();
+ }
+}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage