aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--res/client.js143
-rw-r--r--src/client/Main.hx4
-rw-r--r--src/client/Player.hx6
-rw-r--r--src/client/players/Peertube.hx136
5 files changed, 281 insertions, 9 deletions
diff --git a/README.md b/README.md
index 1af37ff..58b3444 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@ Default channel example: https://synctube.onrender.com/
- Youtube (videos, shorts, streams and playlists)
- [Streamable](https://streamable.com)
- [VK](https://vk.com/video)
+- [Peertube](https://joinpeertube.org) (with `pt:` url prefix)
- Raw mp4 videos and m3u8 playlists (or any other media format supported in browser)
- Iframes (without sync)
diff --git a/res/client.js b/res/client.js
index 7946acf..fa4631f 100644
--- a/res/client.js
+++ b/res/client.js
@@ -1598,8 +1598,10 @@ client_Main.prototype = {
var port = $global.location.port;
url = "" + protocol + "//" + host + (port.length > 0 ? ":" + port : port) + url;
}
- if(!StringTools.startsWith(url,"http")) {
- url = "" + protocol + "//" + url;
+ if(!StringTools.startsWith(url,"pt:")) {
+ if(!StringTools.startsWith(url,"http")) {
+ url = "" + protocol + "//" + url;
+ }
}
this.player.getVideoData({ url : url, atEnd : atEnd},function(data) {
if(data.duration == 0) {
@@ -1677,7 +1679,7 @@ client_Main.prototype = {
var data = JSON.parse(e.data);
if(this.config != null && this.config.isVerbose) {
var t = data.type;
- haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 457, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]});
+ haxe_Log.trace("Event: " + data.type,{ fileName : "src/client/Main.hx", lineNumber : 459, className : "client.Main", methodName : "onMessage", customParams : [Reflect.field(data,t.charAt(0).toLowerCase() + HxOverrides.substr(t,1,null))]});
}
client_JsApi.fireEvents(data);
switch(data.type) {
@@ -2700,8 +2702,7 @@ var client_Player = function(main) {
var _gthis = this;
this.main = main;
this.youtube = new client_players_Youtube(main,this);
- this.streamable = new client_players_Streamable(main,this);
- this.players = [this.youtube,new client_players_Vk(main,this),this.streamable];
+ this.players = [this.youtube,new client_players_Vk(main,this),new client_players_Streamable(main,this),new client_players_Peertube(main,this)];
this.iframePlayer = new client_players_Iframe(main,this);
this.rawPlayer = new client_players_Raw(main,this);
this.initItemButtons();
@@ -4121,6 +4122,138 @@ client_players_Raw.prototype = {
this.video.muted = false;
}
};
+var client_players_Peertube = function(main,player) {
+ this.matchOldPeertube = new EReg("^pt:.+/videos/watch/([-A-z0-9]+)","g");
+ this.matchPeertube = new EReg("^pt:.+/w/(p/)?([A-z0-9]+)","g");
+ client_players_Raw.call(this,main,player);
+};
+client_players_Peertube.__name__ = true;
+client_players_Peertube.__super__ = client_players_Raw;
+client_players_Peertube.prototype = $extend(client_players_Raw.prototype,{
+ isSupportedLink: function(url) {
+ return this.extractVideoId(url).length > 0;
+ }
+ ,extractVideoId: function(url) {
+ if(this.matchPeertube.match(url)) {
+ return this.matchPeertube.matched(2);
+ }
+ if(this.matchOldPeertube.match(url)) {
+ return this.matchOldPeertube.matched(1);
+ }
+ return "";
+ }
+ ,getVideoData: function(data,callback) {
+ var _gthis = this;
+ if(!this.isSupportedLink(data.url)) {
+ this.getRawVideoData(data,callback);
+ return;
+ }
+ this.getPeertubeVideoData(data.url,function(info) {
+ if(info == null) {
+ callback({ duration : 0});
+ return;
+ }
+ _gthis.getRawVideoData({ url : info.url, atEnd : data.atEnd},function(data) {
+ data.url = info.url;
+ data.title = info.title;
+ callback(data);
+ });
+ });
+ }
+ ,getRawVideoData: function(data,callback) {
+ client_players_Raw.prototype.getVideoData.call(this,data,callback);
+ }
+ ,getPeertubeVideoData: function(url,callback) {
+ var _gthis = this;
+ var id = this.extractVideoId(url);
+ url = StringTools.replace(url,"pt:","");
+ if(!StringTools.startsWith(url,"http")) {
+ url = "https://" + url;
+ }
+ var urlObj;
+ try {
+ urlObj = new URL(url);
+ } catch( _g ) {
+ haxe_Log.trace(haxe_Exception.caught(_g),{ fileName : "src/client/players/Peertube.hx", lineNumber : 53, className : "client.players.Peertube", methodName : "getPeertubeVideoData"});
+ callback(null);
+ return;
+ }
+ var host = urlObj.host;
+ if(url.indexOf("/p/") != -1) {
+ var http = new haxe_http_HttpJs("https://" + host + "/api/v1/video-playlists/" + id + "/videos");
+ http.onData = function(data) {
+ var arr = JSON.parse(data).data;
+ var tmp = urlObj.searchParams.get("playlistPosition");
+ var pos = Std.parseInt(tmp != null ? tmp : "1");
+ var tmp = Lambda.find(arr,function(item) {
+ return item.position == pos;
+ });
+ var item = tmp != null ? tmp : arr[0];
+ var uuid;
+ var tmp = item.video;
+ var tmp1 = tmp != null ? tmp.uuid : null;
+ if(tmp1 != null) {
+ uuid = tmp1;
+ } else {
+ haxe_Log.trace(item,{ fileName : "src/client/players/Peertube.hx", lineNumber : 69, className : "client.players.Peertube", methodName : "getPeertubeVideoData"});
+ callback(null);
+ return;
+ }
+ _gthis.getPeertubeVideoData("pt:https://" + host + "/videos/watch/" + uuid,callback);
+ };
+ http.onError = function(err) {
+ haxe_Log.trace(err,{ fileName : "src/client/players/Peertube.hx", lineNumber : 76, className : "client.players.Peertube", methodName : "getPeertubeVideoData"});
+ callback(null);
+ };
+ http.request();
+ return;
+ }
+ var http = new haxe_http_HttpJs("https://" + host + "/api/v1/videos/" + id);
+ http.onData = function(data) {
+ try {
+ var json = JSON.parse(data);
+ var tmp = json.name;
+ var title = tmp != null ? tmp : "PeerTube video";
+ var playlistUrl = _gthis.getBestHlsPlaylistUrl(json);
+ if(playlistUrl != null) {
+ callback({ url : playlistUrl, title : title});
+ return;
+ }
+ var mp4Url = _gthis.getBestMp4Url(json);
+ if(mp4Url != null) {
+ callback({ url : mp4Url, title : title});
+ return;
+ }
+ callback(null);
+ } catch( _g ) {
+ haxe_Log.trace(haxe_Exception.caught(_g),{ fileName : "src/client/players/Peertube.hx", lineNumber : 109, className : "client.players.Peertube", methodName : "getPeertubeVideoData"});
+ callback(null);
+ }
+ };
+ http.onError = function(err) {
+ haxe_Log.trace(err,{ fileName : "src/client/players/Peertube.hx", lineNumber : 115, className : "client.players.Peertube", methodName : "getPeertubeVideoData"});
+ callback(null);
+ };
+ http.request();
+ }
+ ,getBestHlsPlaylistUrl: function(json) {
+ var playlists = json.streamingPlaylists;
+ if(playlists == null || playlists.length == 0) {
+ return null;
+ }
+ return playlists[0].playlistUrl;
+ }
+ ,getBestMp4Url: function(json) {
+ var files = json.files;
+ if(files == null || files.length == 0) {
+ return null;
+ }
+ files.sort(function(a,b) {
+ return b.resolution.id - a.resolution.id;
+ });
+ return files[0].fileDownloadUrl;
+ }
+});
var client_players_RawSubs = function() { };
client_players_RawSubs.__name__ = true;
client_players_RawSubs.loadSubs = function(subsUrl,video) {
diff --git a/src/client/Main.hx b/src/client/Main.hx
index 660693b..42030dd 100644
--- a/src/client/Main.hx
+++ b/src/client/Main.hx
@@ -330,7 +330,9 @@ class Main {
final colonPort = port.length > 0 ? ':$port' : port;
url = '$protocol//$host$colonPort$url';
}
- if (!url.startsWith("http")) url = '$protocol//$url';
+ if (!url.startsWith("pt:")) {
+ if (!url.startsWith("http")) url = '$protocol//$url';
+ }
final obj:VideoDataRequest = {
url: url,
diff --git a/src/client/Player.hx b/src/client/Player.hx
index 6be8a07..9b22748 100644
--- a/src/client/Player.hx
+++ b/src/client/Player.hx
@@ -6,6 +6,7 @@ import Types.VideoDataRequest;
import Types.VideoItem;
import client.Main.getEl;
import client.players.Iframe;
+import client.players.Peertube;
import client.players.Raw;
import client.players.Streamable;
import client.players.Vk;
@@ -30,7 +31,6 @@ class Player {
var isLoaded = false;
var skipSetTime = false;
var skipSetRate = false;
- var streamable:Streamable;
final voiceOverInput:InputElement = getEl("#voiceoverurl");
var audioTrack:Null<Audio>;
@@ -44,11 +44,11 @@ class Player {
public function new(main:Main):Void {
this.main = main;
youtube = new Youtube(main, this);
- streamable = new Streamable(main, this);
players = [
youtube,
new Vk(main, this),
- streamable,
+ new Streamable(main, this),
+ new Peertube(main, this),
];
iframePlayer = new Iframe(main, this);
rawPlayer = new Raw(main, this);
diff --git a/src/client/players/Peertube.hx b/src/client/players/Peertube.hx
new file mode 100644
index 0000000..e869980
--- /dev/null
+++ b/src/client/players/Peertube.hx
@@ -0,0 +1,136 @@
+package client.players;
+
+import Types.VideoData;
+import Types.VideoDataRequest;
+import haxe.Http;
+import haxe.Json;
+import js.html.URL;
+
+class Peertube extends Raw {
+ final matchPeertube = ~/^pt:.+\/w\/(p\/)?([A-z0-9]+)/g;
+ final matchOldPeertube = ~/^pt:.+\/videos\/watch\/([-A-z0-9]+)/g;
+
+ override function isSupportedLink(url:String):Bool {
+ return extractVideoId(url).length > 0;
+ }
+
+ public function extractVideoId(url:String):String {
+ if (matchPeertube.match(url)) return matchPeertube.matched(2);
+ if (matchOldPeertube.match(url)) return matchOldPeertube.matched(1);
+ return "";
+ }
+
+ override function getVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void) {
+ if (!isSupportedLink(data.url)) {
+ getRawVideoData(data, callback);
+ return;
+ }
+ getPeertubeVideoData(data.url, info -> {
+ if (info == null) {
+ callback({duration: 0});
+ return;
+ }
+ getRawVideoData({url: info.url, atEnd: data.atEnd}, data -> {
+ data.url = info.url;
+ data.title = info.title;
+ callback(data);
+ });
+ });
+ }
+
+ function getRawVideoData(data:VideoDataRequest, callback:(data:VideoData) -> Void) {
+ super.getVideoData(data, callback);
+ }
+
+ function getPeertubeVideoData(url:String, callback:(info:Null<{url:String, title:String}>) -> Void) {
+ final id = extractVideoId(url);
+
+ url = url.replace("pt:", "");
+ if (!url.startsWith("http")) url = 'https://$url';
+ final urlObj = try {
+ new URL(url);
+ } catch (e) {
+ trace(e);
+ callback(null);
+ return;
+ }
+ final host = urlObj.host;
+
+ final isPlaylistItem = url.contains("/p/");
+ if (isPlaylistItem) {
+ final apiUrl = 'https://$host/api/v1/video-playlists/$id/videos';
+ final http = new Http(apiUrl);
+ http.onData = data -> {
+ final json:{data:Array<Dynamic>, total:Int} = Json.parse(data);
+ final arr = json.data;
+ final pos = Std.parseInt(urlObj.searchParams.get("playlistPosition") ?? "1");
+ final item:Dynamic = arr.find(item -> item.position == pos) ?? arr[0];
+ final uuid:String = item.video?.uuid ?? {
+ trace(item);
+ callback(null);
+ return;
+ };
+ getPeertubeVideoData('pt:https://$host/videos/watch/$uuid', callback);
+ }
+ http.onError = err -> {
+ trace(err);
+ callback(null);
+ }
+ http.request();
+ return;
+ }
+
+ final apiUrl = 'https://$host/api/v1/videos/$id';
+ final http = new Http(apiUrl);
+ http.onData = data -> {
+ try {
+ final json = Json.parse(data);
+ final title = json.name ?? "PeerTube video";
+ final playlistUrl = getBestHlsPlaylistUrl(json);
+ if (playlistUrl != null) {
+ callback({
+ url: playlistUrl,
+ title: title
+ });
+ return;
+ }
+
+ final mp4Url = getBestMp4Url(json);
+ if (mp4Url != null) {
+ callback({
+ url: mp4Url,
+ title: title
+ });
+ return;
+ }
+
+ callback(null);
+ } catch (e) {
+ trace(e);
+ callback(null);
+ }
+ }
+
+ http.onError = err -> {
+ trace(err);
+ callback(null);
+ }
+
+ http.request();
+ }
+
+ function getBestHlsPlaylistUrl(json:Dynamic):Null<String> {
+ final playlists:Array<Dynamic> = json.streamingPlaylists;
+ if (playlists == null || playlists.length == 0) return null;
+ return playlists[0].playlistUrl;
+ }
+
+ function getBestMp4Url(json:Dynamic):Null<String> {
+ final files:Array<Dynamic> = json.files;
+ if (files == null || files.length == 0) return null;
+ files.sort(function(a, b) {
+ return b.resolution.id - a.resolution.id;
+ });
+ return files[0].fileDownloadUrl;
+ }
+}
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage