From 39147309da1d162abd2d69aa3d7c7773c0aed69f Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Fri, 18 Aug 2023 10:59:41 +0900 Subject: [PATCH 01/27] add part of feature control embed player --- assets/js/embed.js | 118 +++++++++ assets/js/invidious_iframe_api.js | 383 ++++++++++++++++++++++++++++++ 2 files changed, 501 insertions(+) create mode 100644 assets/js/invidious_iframe_api.js diff --git a/assets/js/embed.js b/assets/js/embed.js index b11b5e5a..a0df24f5 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -62,3 +62,121 @@ addEventListener('load', function (e) { }); } }); + +function return_message(message,target_window){ + if(target_window===undefined){ + target_window = window.parent; + } + let url_params = new URLSearchParams(location.search); + let widgetid = url_params.get('widgetid'); + let additional_info = {from:'invidious_control'}; + if(widgetid!==null){ + additional_info.widgetid = widgetid; + } + if(message.message_kind==='event'&&message.eventname==='timeupdate'||message.eventname==='loadedmetadata'){ + let add_value = {getvolume:player.volume(),getduration:player.duration(),getcurrenttime:player.currentTime(),getplaystatus:player.paused(),getplaybackrate:player.playbackRate(),getloopstatus:player.loop(),getmutestatus:player.muted(),getfullscreenstatus:player.isFullscreen(),getavailableplaybackrates:options.playbackRates,gettitle:player_data.title}; + additional_info['value'] = add_value; + } + if(message.eventname==='error'){ + let add_value = {geterrorcode:player.error().code}; + additional_info['value'] = add_value; + } + message = Object.assign(additional_info,message); + let target_origin = url_params.get('origin'); + if(target_origin===null){ + target_origin = '*'; + } + target_window.postMessage(message,target_origin); +} + +function control_embed_iframe(message){ + let url_params = new URLSearchParams(location.search); + let origin = url_params.get('origin'); + if(origin===null||origin===message.origin){ + let widgetid = url_params.get('widgetid'); + if((widgetid===null&&message.data.widgetid===null)||widgetid===message.data.widgetid&&message.data.target==='invidious_control'){ + switch(message.data.eventname){ + case 'play': + player.play(); + break; + case 'pause': + player.pause(); + break; + case 'getvolume': + return_message({command:'getvolume',value:player.volume(),message_kind:'info_return'},message.source); + break; + case 'setvolume': + player.volume(message.data.value); + break; + case 'getduration': + return_message({command:'getduration',value:player.duration(),message_kind:'info_return'},message.source); + break; + case 'getcurrenttime': + return_message({command:'getcurrenttime',value:player.currentTime(),message_kind:'info_return'},message.source); + break; + case 'seek': + const duration = player.duration(); + let newTime = helpers.clamp(message.data.value, 0, duration); + player.currentTime(newTime); + break; + case 'getplaystatus': + return_message({command:'getplaystatus',value:player.paused(),message_kind:'info_return'},message.source); + break; + case 'getplaybackrate': + return_message({command:'getplaybackrate',value:player.playbackRate(),message_kind:'info_return'},message.source); + break; + case 'setplaybackrate': + player.playbackRate(message.data.value); + break; + case 'getavailableplaybackrates': + return_message({command:'getavailableplaybackrates',value:options.playbackRates,message_kind:'info_return'},message.source); + break; + case 'getloopstatus': + return_message({command:'getloopstatus',value:player.loop(),message_kind:'info_return'},message.source); + break; + case 'setloopstatus': + player.loop(message.data.value); + break; + case 'getmutestatus': + return_message({command:'getmutestatus',value:player.muted(),message_kind:'info_return'},message.source); + break; + case 'setmutestatus': + player.muted(message.data.value); + break; + case 'gettitle': + return_message({command:'gettitle',value:player_data.title,message_kind:'info_return'},message.source); + break; + case 'getfullscreenstatus': + return_message({command:'getfullscreenstatus',value:player.isFullscreen(),message_kind:'info_return'},message.source); + break; + case 'requestfullscreen': + player.requestFullscreen(); + break; + case 'exitfullscreen': + player.exitFullscreen(); + break; + case 'geterrorcode': + return_message({command:'geterrorcode',value:player.error().code,message_kind:'info_return'},message.source); + break; + case 'getplaylist': + + } + } + } +} + +if(new URLSearchParams(location.search).get('enablejsapi')==='1'){ + window.addEventListener('message',control_embed_iframe); + player.on('ended',()=>{return_message({message_kind:'event',eventname:'ended'})}); + player.on('error',()=>{return_message({message_kind:'event',eventname:'error'})}); + player.on('ratechange',()=>{return_message({message_kind:'event',eventname:'ratechange'})}); + player.on('volumechange',()=>{return_message({message_kind:'event',eventname:'volumechange'})}); + player.on('waiting',()=>{return_message({message_kind:'event',eventname:'waiting'})}); + player.on('timeupdate',()=>{return_message({message_kind:'event',eventname:'timeupdate'})}); + player.on('loadedmetadata',()=>{return_message({message_kind:'event',eventname:'loadedmetadata'})}); + player.on('play',()=>{return_message({message_kind:'event',eventname:'play'})}); + player.on('seeking',()=>{return_message({message_kind:'event',eventname:'seeking'})}); + player.on('seeked',()=>{return_message({message_kind:'event',eventname:'seeked'})}); + player.on('playerresize',()=>{return_message({message_kind:'event',eventname:'playerresize'})}); + player.on('pause',()=>{return_message({message_kind:'event',eventname:'pause'})}); +} diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js new file mode 100644 index 00000000..e05ea444 --- /dev/null +++ b/assets/js/invidious_iframe_api.js @@ -0,0 +1,383 @@ +class invidious_embed{ + static widgetid = 0; + static eventname_table = {onPlaybackRateChange:'ratechange',onStateChange:'statechange',onerror:'error'}; + static api_promise = false; + addEventListener(eventname,func){ + if(eventname in invidious_embed.eventname_table){ + this.eventobject[invidious_embed.eventname_table[eventname]].push(func); + } + else{ + try{ + this.eventobject[eventname].push(func); + } + catch{} + } + } + removeEventListner(eventname,func){ + var internal_eventname; + if(eventname in invidious_embed.eventname_table){ + internal_eventname = invidious_embed.eventname_table[eventname]; + } + else{ + internal_eventname = eventname; + } + this.eventobject[internal_eventname] = this.eventobject[internal_eventname].fillter(x=>x!==func); + } + Player(element,options){ + this.player_status = -1; + this.error_code = 0; + var replace_elemnt; + if(element===undefined||element===null){ + throw 'please set element id or HTMLElement'; + } + else if(typeof element==='string'){ + replace_elemnt = document.getElementById(element); + } + else{ + replace_elemnt = element; + } + var iframe_src = ''; + if(options.host!==undefined&&options.host!==""){ + iframe_src = new URL(options.host).origin; + } + else{ + iframe_src = 'https://vid.puffyan.us';//I set most hot instanse but this may need discuss or change ay to default instanse + } + this.target_origin = iframe_src.slice(); + iframe_src += '/embed/'; + if(typeof options.videoId==='string'&&options.videoId.length===11){ + iframe_src += options.videoId; + this.videoId = options.videoId; + } + else{ + this.error_code = 2; + this.event_executor('error'); + } + var search_params = new URLSearchParams(''); + search_params.append('widgetid',invidious_embed.widgetid); + this.widgetid = invidious_embed.widgetid; + invidious_embed.widgetid++; + search_params.append('origin',location.origin); + search_params.append('enablejsapi','1'); + if(typeof options.playerVars==='object'){ + this.option_playerVars = options.playerVars; + for(var x in options.playerVars){ + if(typeof x==='string'&&typeof options.playerVars[x]==='string'){ + search_params.append(x,options.playerVars[x]); + } + } + if(options.playerVars.autoplay===undefined){ + search_params.append('autoplay','0'); + } + } + iframe_src += "?" + search_params.toString(); + if(options.events!==undefined&&typeof options.events==='object'){ + for(let x in options.events){ + this.addEventListener(x,options.events[x]); + } + } + this.player_iframe = document.createElement("iframe"); + this.loaded = false; + this.addEventListener('loadedmetadata',()=>{this.event_executor('ready');this.loaded=true}); + this.player_iframe.src = iframe_src; + if(options.width!==undefined&&typeof options.width==='number'){ + this.player_iframe.width = options.width; + } + else{ + if(document.body.clientWidth < 640){ + this.player_iframe.width = document.body.clientWidth; + } + else{ + this.player_iframe.width = 640; + } + } + if(options.width!==undefined&&typeof options.width==='number'){ + this.player_iframe.width = options.width; + } + else{ + this.player_iframe.height = this.player_iframe.width * (9/16); + } + this.player_iframe.style.border = "none"; + this.eventobject = {ready:[],ended:[],error:[],ratechange:[],volumechange:[],waiting:[],timeupdate:[],loadedmetadata:[],play:[],seeking:[],seeked:[],playerresize:[],pause:[],statechange:[]}; + replace_elemnt.replaceWith(this.player_iframe); + this.eventdata = {}; + return this; + } + postMessage(data){ + var additionalInfo = {'origin':location.origin,'widgetid':String(this.widgetid),'target':'invidious_control'}; + data = Object.assign(additionalInfo,data); + this.player_iframe.contentWindow.postMessage(data,this.target_origin); + } + event_executor(eventname){ + var execute_functions = this.eventobject[eventname]; + var return_data = {data:undefined,target:this}; + if(eventname==='statechange'){ + return_data.data = this.getPlayerState(); + } + for(var x=0;x{res_outer=res}); + return {'promise':pro,'resolve':res_outer}; + } + promise_send_event(event_name){ + if(invidious_embed.api_promise){ + var pro_object = this.promise_resolve(); + this.message_wait[event_name].push(pro_object.resolve); + this.postMessage({eventname:event_name}); + return pro_object.promise; + } + else{ + return this.eventdata[event_name]; + } + } + getPlayerState(){ + return this.player_status; + } + playVideo(){ + this.postMessage({eventname:'play'}); + } + pauseVideo(){ + this.postMessage({eventname:'pause'}); + } + getVolume(){ + return this.promise_send_event('getvolume'); + } + setVolume(volume){ + volume = Number(volume); + if(volume!==NaN&&volume!=undefined&&volume>=0&&volume<=100){ + this.postMessage({eventname:'setvolume',value:volume/100}); + } + } + getIframe(){ + return this.player_iframe; + } + destroy(){ + this.player_iframe.remove(); + } + mute(){ + this.postMessage({eventname:'setmutestatus',value:true}); + } + unMute(){ + this.postMessage({eventname:'setmutestatus',value:false}); + } + isMuted(){ + return this.promise_send_event('getmutestatus'); + } + seekTo(seconds,allowSeekAhead){//seconds must be a number and allowSeekAhead is ignore + seconds = Number(seconds); + if(seconds!==NaN&&seconds!==undefined){ + this.postMessage({eventname:'seek',value:seconds}); + } + } + setSize(width,height){//width and height must be Number + this.player_iframe.width = Number(width); + this.player_iframe.height = Number(height); + } + getPlaybackRate(){ + return this.promise_send_event('getplaybackrate'); + } + setPlaybackRate(suggestedRate){//suggestedRate must be number.this player allow not available playback rate such as 1.4 + suggestedRate = Number(suggestedRate); + if(suggestedRate!==NaN&&suggestedRate!==undefined){ + this.postMessage({eventname:'setplaybackrate',value:suggestedRate}); + } + } + getAvailablePlaybackRates(){ + return this.promise_send_event('getavailableplaybackrates'); + } + playOtherVideoById(option,autoplay,startSeconds_arg){//internal fuction + let videoId = ''; + let startSeconds = -1; + let endSeconds = -1; + let mediaContetUrl = ''; + if(typeof option==='string'){ + if(option.length===11){ + videoId = option + } + else{ + mediaContetUrl = option; + } + if(startSeconds_arg!==undefined&&typeof startSeconds_arg==='number'){ + startSeconds = startSeconds_arg; + } + } + else if(typeof option==='object'){ + if(option.videoId!==undefined&&typeof option.videoId==='string'){ + if(option.videoId.length==11){ + videoId = option.videoId; + } + else{ + this.error_code = 2; + this.event_executor('error'); + } + } + else if(option.mediaContentUrl!==undefined&&typeof option.mediaContentUrl==='string'){ + mediaContetUrl = option.mediaContentUrl; + } + else{ + this.error_code = 2; + this.event_executor('error'); + } + if(option.startSeconds!==undefined&&typeof option.startSeconds==='number'&&option.startSeconds>=0){ + startSeconds = option.startSeconds; + } + if(option.endSeconds!==undefined&&typeof option.endSeconds==='number'&&option.endSeconds>=0){ + startSeconds = option.endSeconds; + } + } + if(mediaContetUrl.length>0){ + var tmp_videoId = ''; + if(mediaContetUrl.indexOf('/v/')!==-1){ + var end_pos = mediaContetUrl.length-1; + if(mediaContetUrl.indexOf('?')!==-1){ + end_pos = mediaContetUrl.indexOf('?'); + } + tmp_videoId = mediaContetUrl.substring(mediaContetUrl.indexOf('/v/'),end_pos); + } + else{ + tmp_videoId = new URL(mediaContetUrl).searchParams.get('v'); + } + if(tmp_videoId===null||tmp_videoId.length!==11){ + this.error_code = 2; + this.event_executor('error'); + } + videoId = tmp_videoId; + } + var iframe_sorce = this.target_origin.slice(); + iframe_sorce += "/embed/" + videoId; + this.videoId = videoId; + var search_params = new URLSearchParams(''); + search_params.append('origin',location.origin); + search_params.append('enablejsapi','1'); + search_params.append('widgetid',invidious_embed.widgetid); + this.widgetid = invidious_embed.widgetid; + invidious_embed.widgetid++; + if(autoplay){ + search_params.append('autoplay',1); + } + else{ + search_params.append('autoplay',0); + } + if(this.option_playerVars!==undefined){ + for(var x in this.option_playerVars){ + if(x!=='autoplay'&&x!=='start'&&x!=='end'){ + search_params.append(x,this.option_playerVars[x]); + } + } + } + if(startSeconds!==-1&&startSeconds>=0){ + search_params.append('start',startSeconds); + } + if(endSeconds!==-1&&endSeconds>=0){ + if(endSeconds>startSeconds){ + search_params.append('end',endSeconds); + } + else{ + throw 'invalid end seconds'; + } + } + iframe_sorce += "?" + search_params.toString(); + this.player_iframe.src = iframe_sorce; + if(autoplay){ + this.player_status = 5; + } + this.eventdata = {}; + } + loadVideoById(option,startSeconds){ + this.playOtherVideoById(option,true,startSeconds); + } + cueVideoById(option,startSeconds){ + this.playOtherVideoById(option,false,startSeconds); + } + cuevVideoByUrl(option,startSeconds){ + this.playOtherVideoById(option,false,startSeconds); + } + loadVideoByUrl(option,startSeconds){ + this.playOtherVideoById(option,true,startSeconds); + } + getDuration(){ + return this.promise_send_event('getduration'); + } + getVideoUrl(){ + return this.target_origin + "/watch?v=" + this.videoId; + } + getVideoTitle(){//original function + return this.promise_send_event('gettitle'); + } + async getVideoEmbedCode(){ + var title = await this.getVideoTitle(); + return ''; + } + getCurrentTime(){ + return this.promise_send_event('getcurrenttime'); + } + constructor(element,options){ + this.Player(element,options); + window.addEventListener('message',(ms)=>{this.receiveMessage(ms)}); + this.message_wait = {getvolume:[],getmutestatus:[],getduration:[],getcurrenttime:[],getplaybackrate:[],getavailableplaybackrates:[],gettitle:[]}; + } +} +function invidious_ready(func){ + if(typeof func==='function'){ + func(); + } +} +const invidious = {Player:invidious_embed,PlayerState:{ENDED:0,PLAYING:1,PAUSED:2,BUFFERING:3,CUED:5},ready:invidious_ready}; +try{ + onInvidiousIframeAPIReady(); +} +catch{} From d6b34a4057cfb6814aeedfdb16c13e76bbd61abc Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:23:52 +0900 Subject: [PATCH 02/27] Revert "add part of feature control embed player" This reverts commit 39147309da1d162abd2d69aa3d7c7773c0aed69f. --- assets/js/embed.js | 118 --------- assets/js/invidious_iframe_api.js | 383 ------------------------------ 2 files changed, 501 deletions(-) delete mode 100644 assets/js/invidious_iframe_api.js diff --git a/assets/js/embed.js b/assets/js/embed.js index a0df24f5..b11b5e5a 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -62,121 +62,3 @@ addEventListener('load', function (e) { }); } }); - -function return_message(message,target_window){ - if(target_window===undefined){ - target_window = window.parent; - } - let url_params = new URLSearchParams(location.search); - let widgetid = url_params.get('widgetid'); - let additional_info = {from:'invidious_control'}; - if(widgetid!==null){ - additional_info.widgetid = widgetid; - } - if(message.message_kind==='event'&&message.eventname==='timeupdate'||message.eventname==='loadedmetadata'){ - let add_value = {getvolume:player.volume(),getduration:player.duration(),getcurrenttime:player.currentTime(),getplaystatus:player.paused(),getplaybackrate:player.playbackRate(),getloopstatus:player.loop(),getmutestatus:player.muted(),getfullscreenstatus:player.isFullscreen(),getavailableplaybackrates:options.playbackRates,gettitle:player_data.title}; - additional_info['value'] = add_value; - } - if(message.eventname==='error'){ - let add_value = {geterrorcode:player.error().code}; - additional_info['value'] = add_value; - } - message = Object.assign(additional_info,message); - let target_origin = url_params.get('origin'); - if(target_origin===null){ - target_origin = '*'; - } - target_window.postMessage(message,target_origin); -} - -function control_embed_iframe(message){ - let url_params = new URLSearchParams(location.search); - let origin = url_params.get('origin'); - if(origin===null||origin===message.origin){ - let widgetid = url_params.get('widgetid'); - if((widgetid===null&&message.data.widgetid===null)||widgetid===message.data.widgetid&&message.data.target==='invidious_control'){ - switch(message.data.eventname){ - case 'play': - player.play(); - break; - case 'pause': - player.pause(); - break; - case 'getvolume': - return_message({command:'getvolume',value:player.volume(),message_kind:'info_return'},message.source); - break; - case 'setvolume': - player.volume(message.data.value); - break; - case 'getduration': - return_message({command:'getduration',value:player.duration(),message_kind:'info_return'},message.source); - break; - case 'getcurrenttime': - return_message({command:'getcurrenttime',value:player.currentTime(),message_kind:'info_return'},message.source); - break; - case 'seek': - const duration = player.duration(); - let newTime = helpers.clamp(message.data.value, 0, duration); - player.currentTime(newTime); - break; - case 'getplaystatus': - return_message({command:'getplaystatus',value:player.paused(),message_kind:'info_return'},message.source); - break; - case 'getplaybackrate': - return_message({command:'getplaybackrate',value:player.playbackRate(),message_kind:'info_return'},message.source); - break; - case 'setplaybackrate': - player.playbackRate(message.data.value); - break; - case 'getavailableplaybackrates': - return_message({command:'getavailableplaybackrates',value:options.playbackRates,message_kind:'info_return'},message.source); - break; - case 'getloopstatus': - return_message({command:'getloopstatus',value:player.loop(),message_kind:'info_return'},message.source); - break; - case 'setloopstatus': - player.loop(message.data.value); - break; - case 'getmutestatus': - return_message({command:'getmutestatus',value:player.muted(),message_kind:'info_return'},message.source); - break; - case 'setmutestatus': - player.muted(message.data.value); - break; - case 'gettitle': - return_message({command:'gettitle',value:player_data.title,message_kind:'info_return'},message.source); - break; - case 'getfullscreenstatus': - return_message({command:'getfullscreenstatus',value:player.isFullscreen(),message_kind:'info_return'},message.source); - break; - case 'requestfullscreen': - player.requestFullscreen(); - break; - case 'exitfullscreen': - player.exitFullscreen(); - break; - case 'geterrorcode': - return_message({command:'geterrorcode',value:player.error().code,message_kind:'info_return'},message.source); - break; - case 'getplaylist': - - } - } - } -} - -if(new URLSearchParams(location.search).get('enablejsapi')==='1'){ - window.addEventListener('message',control_embed_iframe); - player.on('ended',()=>{return_message({message_kind:'event',eventname:'ended'})}); - player.on('error',()=>{return_message({message_kind:'event',eventname:'error'})}); - player.on('ratechange',()=>{return_message({message_kind:'event',eventname:'ratechange'})}); - player.on('volumechange',()=>{return_message({message_kind:'event',eventname:'volumechange'})}); - player.on('waiting',()=>{return_message({message_kind:'event',eventname:'waiting'})}); - player.on('timeupdate',()=>{return_message({message_kind:'event',eventname:'timeupdate'})}); - player.on('loadedmetadata',()=>{return_message({message_kind:'event',eventname:'loadedmetadata'})}); - player.on('play',()=>{return_message({message_kind:'event',eventname:'play'})}); - player.on('seeking',()=>{return_message({message_kind:'event',eventname:'seeking'})}); - player.on('seeked',()=>{return_message({message_kind:'event',eventname:'seeked'})}); - player.on('playerresize',()=>{return_message({message_kind:'event',eventname:'playerresize'})}); - player.on('pause',()=>{return_message({message_kind:'event',eventname:'pause'})}); -} diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js deleted file mode 100644 index e05ea444..00000000 --- a/assets/js/invidious_iframe_api.js +++ /dev/null @@ -1,383 +0,0 @@ -class invidious_embed{ - static widgetid = 0; - static eventname_table = {onPlaybackRateChange:'ratechange',onStateChange:'statechange',onerror:'error'}; - static api_promise = false; - addEventListener(eventname,func){ - if(eventname in invidious_embed.eventname_table){ - this.eventobject[invidious_embed.eventname_table[eventname]].push(func); - } - else{ - try{ - this.eventobject[eventname].push(func); - } - catch{} - } - } - removeEventListner(eventname,func){ - var internal_eventname; - if(eventname in invidious_embed.eventname_table){ - internal_eventname = invidious_embed.eventname_table[eventname]; - } - else{ - internal_eventname = eventname; - } - this.eventobject[internal_eventname] = this.eventobject[internal_eventname].fillter(x=>x!==func); - } - Player(element,options){ - this.player_status = -1; - this.error_code = 0; - var replace_elemnt; - if(element===undefined||element===null){ - throw 'please set element id or HTMLElement'; - } - else if(typeof element==='string'){ - replace_elemnt = document.getElementById(element); - } - else{ - replace_elemnt = element; - } - var iframe_src = ''; - if(options.host!==undefined&&options.host!==""){ - iframe_src = new URL(options.host).origin; - } - else{ - iframe_src = 'https://vid.puffyan.us';//I set most hot instanse but this may need discuss or change ay to default instanse - } - this.target_origin = iframe_src.slice(); - iframe_src += '/embed/'; - if(typeof options.videoId==='string'&&options.videoId.length===11){ - iframe_src += options.videoId; - this.videoId = options.videoId; - } - else{ - this.error_code = 2; - this.event_executor('error'); - } - var search_params = new URLSearchParams(''); - search_params.append('widgetid',invidious_embed.widgetid); - this.widgetid = invidious_embed.widgetid; - invidious_embed.widgetid++; - search_params.append('origin',location.origin); - search_params.append('enablejsapi','1'); - if(typeof options.playerVars==='object'){ - this.option_playerVars = options.playerVars; - for(var x in options.playerVars){ - if(typeof x==='string'&&typeof options.playerVars[x]==='string'){ - search_params.append(x,options.playerVars[x]); - } - } - if(options.playerVars.autoplay===undefined){ - search_params.append('autoplay','0'); - } - } - iframe_src += "?" + search_params.toString(); - if(options.events!==undefined&&typeof options.events==='object'){ - for(let x in options.events){ - this.addEventListener(x,options.events[x]); - } - } - this.player_iframe = document.createElement("iframe"); - this.loaded = false; - this.addEventListener('loadedmetadata',()=>{this.event_executor('ready');this.loaded=true}); - this.player_iframe.src = iframe_src; - if(options.width!==undefined&&typeof options.width==='number'){ - this.player_iframe.width = options.width; - } - else{ - if(document.body.clientWidth < 640){ - this.player_iframe.width = document.body.clientWidth; - } - else{ - this.player_iframe.width = 640; - } - } - if(options.width!==undefined&&typeof options.width==='number'){ - this.player_iframe.width = options.width; - } - else{ - this.player_iframe.height = this.player_iframe.width * (9/16); - } - this.player_iframe.style.border = "none"; - this.eventobject = {ready:[],ended:[],error:[],ratechange:[],volumechange:[],waiting:[],timeupdate:[],loadedmetadata:[],play:[],seeking:[],seeked:[],playerresize:[],pause:[],statechange:[]}; - replace_elemnt.replaceWith(this.player_iframe); - this.eventdata = {}; - return this; - } - postMessage(data){ - var additionalInfo = {'origin':location.origin,'widgetid':String(this.widgetid),'target':'invidious_control'}; - data = Object.assign(additionalInfo,data); - this.player_iframe.contentWindow.postMessage(data,this.target_origin); - } - event_executor(eventname){ - var execute_functions = this.eventobject[eventname]; - var return_data = {data:undefined,target:this}; - if(eventname==='statechange'){ - return_data.data = this.getPlayerState(); - } - for(var x=0;x{res_outer=res}); - return {'promise':pro,'resolve':res_outer}; - } - promise_send_event(event_name){ - if(invidious_embed.api_promise){ - var pro_object = this.promise_resolve(); - this.message_wait[event_name].push(pro_object.resolve); - this.postMessage({eventname:event_name}); - return pro_object.promise; - } - else{ - return this.eventdata[event_name]; - } - } - getPlayerState(){ - return this.player_status; - } - playVideo(){ - this.postMessage({eventname:'play'}); - } - pauseVideo(){ - this.postMessage({eventname:'pause'}); - } - getVolume(){ - return this.promise_send_event('getvolume'); - } - setVolume(volume){ - volume = Number(volume); - if(volume!==NaN&&volume!=undefined&&volume>=0&&volume<=100){ - this.postMessage({eventname:'setvolume',value:volume/100}); - } - } - getIframe(){ - return this.player_iframe; - } - destroy(){ - this.player_iframe.remove(); - } - mute(){ - this.postMessage({eventname:'setmutestatus',value:true}); - } - unMute(){ - this.postMessage({eventname:'setmutestatus',value:false}); - } - isMuted(){ - return this.promise_send_event('getmutestatus'); - } - seekTo(seconds,allowSeekAhead){//seconds must be a number and allowSeekAhead is ignore - seconds = Number(seconds); - if(seconds!==NaN&&seconds!==undefined){ - this.postMessage({eventname:'seek',value:seconds}); - } - } - setSize(width,height){//width and height must be Number - this.player_iframe.width = Number(width); - this.player_iframe.height = Number(height); - } - getPlaybackRate(){ - return this.promise_send_event('getplaybackrate'); - } - setPlaybackRate(suggestedRate){//suggestedRate must be number.this player allow not available playback rate such as 1.4 - suggestedRate = Number(suggestedRate); - if(suggestedRate!==NaN&&suggestedRate!==undefined){ - this.postMessage({eventname:'setplaybackrate',value:suggestedRate}); - } - } - getAvailablePlaybackRates(){ - return this.promise_send_event('getavailableplaybackrates'); - } - playOtherVideoById(option,autoplay,startSeconds_arg){//internal fuction - let videoId = ''; - let startSeconds = -1; - let endSeconds = -1; - let mediaContetUrl = ''; - if(typeof option==='string'){ - if(option.length===11){ - videoId = option - } - else{ - mediaContetUrl = option; - } - if(startSeconds_arg!==undefined&&typeof startSeconds_arg==='number'){ - startSeconds = startSeconds_arg; - } - } - else if(typeof option==='object'){ - if(option.videoId!==undefined&&typeof option.videoId==='string'){ - if(option.videoId.length==11){ - videoId = option.videoId; - } - else{ - this.error_code = 2; - this.event_executor('error'); - } - } - else if(option.mediaContentUrl!==undefined&&typeof option.mediaContentUrl==='string'){ - mediaContetUrl = option.mediaContentUrl; - } - else{ - this.error_code = 2; - this.event_executor('error'); - } - if(option.startSeconds!==undefined&&typeof option.startSeconds==='number'&&option.startSeconds>=0){ - startSeconds = option.startSeconds; - } - if(option.endSeconds!==undefined&&typeof option.endSeconds==='number'&&option.endSeconds>=0){ - startSeconds = option.endSeconds; - } - } - if(mediaContetUrl.length>0){ - var tmp_videoId = ''; - if(mediaContetUrl.indexOf('/v/')!==-1){ - var end_pos = mediaContetUrl.length-1; - if(mediaContetUrl.indexOf('?')!==-1){ - end_pos = mediaContetUrl.indexOf('?'); - } - tmp_videoId = mediaContetUrl.substring(mediaContetUrl.indexOf('/v/'),end_pos); - } - else{ - tmp_videoId = new URL(mediaContetUrl).searchParams.get('v'); - } - if(tmp_videoId===null||tmp_videoId.length!==11){ - this.error_code = 2; - this.event_executor('error'); - } - videoId = tmp_videoId; - } - var iframe_sorce = this.target_origin.slice(); - iframe_sorce += "/embed/" + videoId; - this.videoId = videoId; - var search_params = new URLSearchParams(''); - search_params.append('origin',location.origin); - search_params.append('enablejsapi','1'); - search_params.append('widgetid',invidious_embed.widgetid); - this.widgetid = invidious_embed.widgetid; - invidious_embed.widgetid++; - if(autoplay){ - search_params.append('autoplay',1); - } - else{ - search_params.append('autoplay',0); - } - if(this.option_playerVars!==undefined){ - for(var x in this.option_playerVars){ - if(x!=='autoplay'&&x!=='start'&&x!=='end'){ - search_params.append(x,this.option_playerVars[x]); - } - } - } - if(startSeconds!==-1&&startSeconds>=0){ - search_params.append('start',startSeconds); - } - if(endSeconds!==-1&&endSeconds>=0){ - if(endSeconds>startSeconds){ - search_params.append('end',endSeconds); - } - else{ - throw 'invalid end seconds'; - } - } - iframe_sorce += "?" + search_params.toString(); - this.player_iframe.src = iframe_sorce; - if(autoplay){ - this.player_status = 5; - } - this.eventdata = {}; - } - loadVideoById(option,startSeconds){ - this.playOtherVideoById(option,true,startSeconds); - } - cueVideoById(option,startSeconds){ - this.playOtherVideoById(option,false,startSeconds); - } - cuevVideoByUrl(option,startSeconds){ - this.playOtherVideoById(option,false,startSeconds); - } - loadVideoByUrl(option,startSeconds){ - this.playOtherVideoById(option,true,startSeconds); - } - getDuration(){ - return this.promise_send_event('getduration'); - } - getVideoUrl(){ - return this.target_origin + "/watch?v=" + this.videoId; - } - getVideoTitle(){//original function - return this.promise_send_event('gettitle'); - } - async getVideoEmbedCode(){ - var title = await this.getVideoTitle(); - return ''; - } - getCurrentTime(){ - return this.promise_send_event('getcurrenttime'); - } - constructor(element,options){ - this.Player(element,options); - window.addEventListener('message',(ms)=>{this.receiveMessage(ms)}); - this.message_wait = {getvolume:[],getmutestatus:[],getduration:[],getcurrenttime:[],getplaybackrate:[],getavailableplaybackrates:[],gettitle:[]}; - } -} -function invidious_ready(func){ - if(typeof func==='function'){ - func(); - } -} -const invidious = {Player:invidious_embed,PlayerState:{ENDED:0,PLAYING:1,PAUSED:2,BUFFERING:3,CUED:5},ready:invidious_ready}; -try{ - onInvidiousIframeAPIReady(); -} -catch{} From a9820eee5eb50f300fc0488051cb817aa6eaae75 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:40:50 +0900 Subject: [PATCH 03/27] add part of iframe api --- assets/js/embed.js | 118 +++++++++ assets/js/invidious_iframe_api.js | 383 ++++++++++++++++++++++++++++++ 2 files changed, 501 insertions(+) create mode 100644 assets/js/invidious_iframe_api.js diff --git a/assets/js/embed.js b/assets/js/embed.js index b11b5e5a..4b741f6f 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -62,3 +62,121 @@ addEventListener('load', function (e) { }); } }); + +function return_message(message,target_window){ + if(target_window===undefined){ + target_window = window.parent; + } + let url_params = new URLSearchParams(location.search); + let widgetid = url_params.get('widgetid'); + let additional_info = {from:'invidious_control'}; + if(widgetid!==null){ + additional_info.widgetid = widgetid; + } + if(message.message_kind==='event'&&message.eventname==='timeupdate'||message.eventname==='loadedmetadata'){ + let add_value = {getvolume:player.volume(),getduration:player.duration(),getcurrenttime:player.currentTime(),getplaystatus:player.paused(),getplaybackrate:player.playbackRate(),getloopstatus:player.loop(),getmutestatus:player.muted(),getfullscreenstatus:player.isFullscreen(),getavailableplaybackrates:options.playbackRates,gettitle:player_data.title}; + additional_info['value'] = add_value; + } + if(message.eventname==='error'){ + let add_value = {geterrorcode:player.error().code}; + additional_info['value'] = add_value; + } + message = Object.assign(additional_info,message); + let target_origin = url_params.get('origin'); + if(target_origin===null){ + target_origin = '*'; + } + target_window.postMessage(message,target_origin); +} + +function control_embed_iframe(message){ + let url_params = new URLSearchParams(location.search); + let origin = url_params.get('origin'); + if(origin===null||origin===message.origin){ + let widgetid = url_params.get('widgetid'); + if((widgetid===null&&message.data.widgetid===null)||widgetid===message.data.widgetid&&message.data.target==='invidious_control'){ + switch(message.data.eventname){ + case 'play': + player.play(); + break; + case 'pause': + player.pause(); + break; + case 'getvolume': + return_message({command:'getvolume',value:player.volume(),message_kind:'info_return'},message.source); + break; + case 'setvolume': + player.volume(message.data.value); + break; + case 'getduration': + return_message({command:'getduration',value:player.duration(),message_kind:'info_return'},message.source); + break; + case 'getcurrenttime': + return_message({command:'getcurrenttime',value:player.currentTime(),message_kind:'info_return'},message.source); + break; + case 'seek': + const duration = player.duration(); + let newTime = helpers.clamp(message.data.value, 0, duration); + player.currentTime(newTime); + break; + case 'getplaystatus': + return_message({command:'getplaystatus',value:player.paused(),message_kind:'info_return'},message.source); + break; + case 'getplaybackrate': + return_message({command:'getplaybackrate',value:player.playbackRate(),message_kind:'info_return'},message.source); + break; + case 'setplaybackrate': + player.playbackRate(message.data.value); + break; + case 'getavailableplaybackrates': + return_message({command:'getavailableplaybackrates',value:options.playbackRates,message_kind:'info_return'},message.source); + break; + case 'getloopstatus': + return_message({command:'getloopstatus',value:player.loop(),message_kind:'info_return'},message.source); + break; + case 'setloopstatus': + player.loop(message.data.value); + break; + case 'getmutestatus': + return_message({command:'getmutestatus',value:player.muted(),message_kind:'info_return'},message.source); + break; + case 'setmutestatus': + player.muted(message.data.value); + break; + case 'gettitle': + return_message({command:'gettitle',value:player_data.title,message_kind:'info_return'},message.source); + break; + case 'getfullscreenstatus': + return_message({command:'getfullscreenstatus',value:player.isFullscreen(),message_kind:'info_return'},message.source); + break; + case 'requestfullscreen': + player.requestFullscreen(); + break; + case 'exitfullscreen': + player.exitFullscreen(); + break; + case 'geterrorcode': + return_message({command:'geterrorcode',value:player.error().code,message_kind:'info_return'},message.source); + break; + case 'getplaylist': + + } + } + } +} + +if(new URLSearchParams(location.search).get('enablejsapi')==='1'){ + window.addEventListener('message',control_embed_iframe); + player.on('ended',function(){return_message({message_kind:'event',eventname:'ended'})}); + player.on('error',function(){return_message({message_kind:'event',eventname:'error'})}); + player.on('ratechange',function(){return_message({message_kind:'event',eventname:'ratechange'})}); + player.on('volumechange',function(){return_message({message_kind:'event',eventname:'volumechange'})}); + player.on('waiting',function(){return_message({message_kind:'event',eventname:'waiting'})}); + player.on('timeupdate',function(){return_message({message_kind:'event',eventname:'timeupdate'})}); + player.on('loadedmetadata',function(){return_message({message_kind:'event',eventname:'loadedmetadata'})}); + player.on('play',function(){return_message({message_kind:'event',eventname:'play'})}); + player.on('seeking',function(){return_message({message_kind:'event',eventname:'seeking'})}); + player.on('seeked',function(){return_message({message_kind:'event',eventname:'seeked'})}); + player.on('playerresize',function(){return_message({message_kind:'event',eventname:'playerresize'})}); + player.on('pause',function(){return_message({message_kind:'event',eventname:'pause'})}); +} \ No newline at end of file diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js new file mode 100644 index 00000000..cc8a259f --- /dev/null +++ b/assets/js/invidious_iframe_api.js @@ -0,0 +1,383 @@ +class invidious_embed{ + static widgetid = 0; + static eventname_table = {onPlaybackRateChange:'ratechange',onStateChange:'statechange',onerror:'error'}; + static api_promise = false; + addEventListener(eventname,func){ + if(eventname in invidious_embed.eventname_table){ + this.eventobject[invidious_embed.eventname_table[eventname]].push(func); + } + else{ + try{ + this.eventobject[eventname].push(func); + } + catch{} + } + } + removeEventListner(eventname,func){ + var internal_eventname; + if(eventname in invidious_embed.eventname_table){ + internal_eventname = invidious_embed.eventname_table[eventname]; + } + else{ + internal_eventname = eventname; + } + this.eventobject[internal_eventname] = this.eventobject[internal_eventname].fillter(x=>x!==func); + } + Player(element,options){ + this.player_status = -1; + this.error_code = 0; + var replace_elemnt; + if(element===undefined||element===null){ + throw 'please set element id or HTMLElement'; + } + else if(typeof element==='string'){ + replace_elemnt = document.getElementById(element); + } + else{ + replace_elemnt = element; + } + var iframe_src = ''; + if(options.host!==undefined&&options.host!==""){ + iframe_src = new URL(options.host).origin; + } + else{ + iframe_src = 'https://vid.puffyan.us';//I set most hot instanse but this may need discuss or change ay to default instanse + } + this.target_origin = iframe_src.slice(); + iframe_src += '/embed/'; + if(typeof options.videoId==='string'&&options.videoId.length===11){ + iframe_src += options.videoId; + this.videoId = options.videoId; + } + else{ + this.error_code = 2; + this.event_executor('error'); + } + var search_params = new URLSearchParams(''); + search_params.append('widgetid',invidious_embed.widgetid); + this.widgetid = invidious_embed.widgetid; + invidious_embed.widgetid++; + search_params.append('origin',location.origin); + search_params.append('enablejsapi','1'); + if(typeof options.playerVars==='object'){ + this.option_playerVars = options.playerVars; + for(var x in options.playerVars){ + if(typeof x==='string'&&typeof options.playerVars[x]==='string'){ + search_params.append(x,options.playerVars[x]); + } + } + if(options.playerVars.autoplay===undefined){ + search_params.append('autoplay','0'); + } + } + iframe_src += "?" + search_params.toString(); + if(options.events!==undefined&&typeof options.events==='object'){ + for(let x in options.events){ + this.addEventListener(x,options.events[x]); + } + } + this.player_iframe = document.createElement("iframe"); + this.loaded = false; + this.addEventListener('loadedmetadata',()=>{this.event_executor('ready');this.loaded=true}); + this.player_iframe.src = iframe_src; + if(options.width!==undefined&&typeof options.width==='number'){ + this.player_iframe.width = options.width; + } + else{ + if(document.body.clientWidth < 640){ + this.player_iframe.width = document.body.clientWidth; + } + else{ + this.player_iframe.width = 640; + } + } + if(options.width!==undefined&&typeof options.width==='number'){ + this.player_iframe.width = options.width; + } + else{ + this.player_iframe.height = this.player_iframe.width * (9/16); + } + this.player_iframe.style.border = "none"; + this.eventobject = {ready:[],ended:[],error:[],ratechange:[],volumechange:[],waiting:[],timeupdate:[],loadedmetadata:[],play:[],seeking:[],seeked:[],playerresize:[],pause:[],statechange:[]}; + replace_elemnt.replaceWith(this.player_iframe); + this.eventdata = {}; + return this; + } + postMessage(data){ + var additionalInfo = {'origin':location.origin,'widgetid':String(this.widgetid),'target':'invidious_control'}; + data = Object.assign(additionalInfo,data); + this.player_iframe.contentWindow.postMessage(data,this.target_origin); + } + event_executor(eventname){ + var execute_functions = this.eventobject[eventname]; + var return_data = {data:undefined,target:this}; + if(eventname==='statechange'){ + return_data.data = this.getPlayerState(); + } + for(var x=0;x{res_outer=res}); + return {'promise':pro,'resolve':res_outer}; + } + promise_send_event(event_name){ + if(invidious_embed.api_promise){ + var pro_object = this.promise_resolve(); + this.message_wait[event_name].push(pro_object.resolve); + this.postMessage({eventname:event_name}); + return pro_object.promise; + } + else{ + return this.eventdata[event_name]; + } + } + getPlayerState(){ + return this.player_status; + } + playVideo(){ + this.postMessage({eventname:'play'}); + } + pauseVideo(){ + this.postMessage({eventname:'pause'}); + } + getVolume(){ + return this.promise_send_event('getvolume'); + } + setVolume(volume){ + volume = Number(volume); + if(volume!==NaN&&volume!=undefined&&volume>=0&&volume<=100){ + this.postMessage({eventname:'setvolume',value:volume/100}); + } + } + getIframe(){ + return this.player_iframe; + } + destroy(){ + this.player_iframe.remove(); + } + mute(){ + this.postMessage({eventname:'setmutestatus',value:true}); + } + unMute(){ + this.postMessage({eventname:'setmutestatus',value:false}); + } + isMuted(){ + return this.promise_send_event('getmutestatus'); + } + seekTo(seconds,allowSeekAhead){//seconds must be a number and allowSeekAhead is ignore + seconds = Number(seconds); + if(seconds!==NaN&&seconds!==undefined){ + this.postMessage({eventname:'seek',value:seconds}); + } + } + setSize(width,height){//width and height must be Number + this.player_iframe.width = Number(width); + this.player_iframe.height = Number(height); + } + getPlaybackRate(){ + return this.promise_send_event('getplaybackrate'); + } + setPlaybackRate(suggestedRate){//suggestedRate must be number.this player allow not available playback rate such as 1.4 + suggestedRate = Number(suggestedRate); + if(suggestedRate!==NaN&&suggestedRate!==undefined){ + this.postMessage({eventname:'setplaybackrate',value:suggestedRate}); + } + } + getAvailablePlaybackRates(){ + return this.promise_send_event('getavailableplaybackrates'); + } + playOtherVideoById(option,autoplay,startSeconds_arg){//internal fuction + let videoId = ''; + let startSeconds = -1; + let endSeconds = -1; + let mediaContetUrl = ''; + if(typeof option==='string'){ + if(option.length===11){ + videoId = option + } + else{ + mediaContetUrl = option; + } + if(startSeconds_arg!==undefined&&typeof startSeconds_arg==='number'){ + startSeconds = startSeconds_arg; + } + } + else if(typeof option==='object'){ + if(option.videoId!==undefined&&typeof option.videoId==='string'){ + if(option.videoId.length==11){ + videoId = option.videoId; + } + else{ + this.error_code = 2; + this.event_executor('error'); + } + } + else if(option.mediaContentUrl!==undefined&&typeof option.mediaContentUrl==='string'){ + mediaContetUrl = option.mediaContentUrl; + } + else{ + this.error_code = 2; + this.event_executor('error'); + } + if(option.startSeconds!==undefined&&typeof option.startSeconds==='number'&&option.startSeconds>=0){ + startSeconds = option.startSeconds; + } + if(option.endSeconds!==undefined&&typeof option.endSeconds==='number'&&option.endSeconds>=0){ + startSeconds = option.endSeconds; + } + } + if(mediaContetUrl.length>0){ + var tmp_videoId = ''; + if(mediaContetUrl.indexOf('/v/')!==-1){ + var end_pos = mediaContetUrl.length-1; + if(mediaContetUrl.indexOf('?')!==-1){ + end_pos = mediaContetUrl.indexOf('?'); + } + tmp_videoId = mediaContetUrl.substring(mediaContetUrl.indexOf('/v/'),end_pos); + } + else{ + tmp_videoId = new URL(mediaContetUrl).searchParams.get('v'); + } + if(tmp_videoId===null||tmp_videoId.length!==11){ + this.error_code = 2; + this.event_executor('error'); + } + videoId = tmp_videoId; + } + var iframe_sorce = this.target_origin.slice(); + iframe_sorce += "/embed/" + videoId; + this.videoId = videoId; + var search_params = new URLSearchParams(''); + search_params.append('origin',location.origin); + search_params.append('enablejsapi','1'); + search_params.append('widgetid',invidious_embed.widgetid); + this.widgetid = invidious_embed.widgetid; + invidious_embed.widgetid++; + if(autoplay){ + search_params.append('autoplay',1); + } + else{ + search_params.append('autoplay',0); + } + if(this.option_playerVars!==undefined){ + for(var x in this.option_playerVars){ + if(x!=='autoplay'&&x!=='start'&&x!=='end'){ + search_params.append(x,this.option_playerVars[x]); + } + } + } + if(startSeconds!==-1&&startSeconds>=0){ + search_params.append('start',startSeconds); + } + if(endSeconds!==-1&&endSeconds>=0){ + if(endSeconds>startSeconds){ + search_params.append('end',endSeconds); + } + else{ + throw 'invalid end seconds'; + } + } + iframe_sorce += "?" + search_params.toString(); + this.player_iframe.src = iframe_sorce; + if(autoplay){ + this.player_status = 5; + } + this.eventdata = {}; + } + loadVideoById(option,startSeconds){ + this.playOtherVideoById(option,true,startSeconds); + } + cueVideoById(option,startSeconds){ + this.playOtherVideoById(option,false,startSeconds); + } + cuevVideoByUrl(option,startSeconds){ + this.playOtherVideoById(option,false,startSeconds); + } + loadVideoByUrl(option,startSeconds){ + this.playOtherVideoById(option,true,startSeconds); + } + getDuration(){ + return this.promise_send_event('getduration'); + } + getVideoUrl(){ + return this.target_origin + "/watch?v=" + this.videoId; + } + getVideoTitle(){//original function + return this.promise_send_event('gettitle'); + } + async getVideoEmbedCode(){ + var title = await this.getVideoTitle(); + return ''; + } + getCurrentTime(){ + return this.promise_send_event('getcurrenttime'); + } + constructor(element,options){ + this.Player(element,options); + window.addEventListener('message',(ms)=>{this.receiveMessage(ms)}); + this.message_wait = {getvolume:[],getmutestatus:[],getduration:[],getcurrenttime:[],getplaybackrate:[],getavailableplaybackrates:[],gettitle:[]}; + } +} +function invidious_ready(func){ + if(typeof func==='function'){ + func(); + } +} +const invidious = {Player:invidious_embed,PlayerState:{ENDED:0,PLAYING:1,PAUSED:2,BUFFERING:3,CUED:5},ready:invidious_ready}; +try{ + onInvidiousIframeAPIReady(); +} +catch{} From 54c90bae48cad248d32cd70070786e97f8dfff49 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Fri, 18 Aug 2023 11:47:14 +0900 Subject: [PATCH 04/27] add end new line --- assets/js/embed.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 4b741f6f..1ab8bc5f 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -179,4 +179,4 @@ if(new URLSearchParams(location.search).get('enablejsapi')==='1'){ player.on('seeked',function(){return_message({message_kind:'event',eventname:'seeked'})}); player.on('playerresize',function(){return_message({message_kind:'event',eventname:'playerresize'})}); player.on('pause',function(){return_message({message_kind:'event',eventname:'pause'})}); -} \ No newline at end of file +} From fc49ab285e00391f1b822c6dd39e0ad89239ac7d Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Sat, 26 Aug 2023 00:48:41 +0900 Subject: [PATCH 05/27] fix playing related bugs --- assets/js/invidious_iframe_api.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index cc8a259f..d08829f1 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -26,6 +26,8 @@ class invidious_embed{ Player(element,options){ this.player_status = -1; this.error_code = 0; + this.volume = 100; + this.eventobject = {ready:[],ended:[],error:[],ratechange:[],volumechange:[],waiting:[],timeupdate:[],loadedmetadata:[],play:[],seeking:[],seeked:[],playerresize:[],pause:[],statechange:[]}; var replace_elemnt; if(element===undefined||element===null){ throw 'please set element id or HTMLElement'; @@ -79,6 +81,7 @@ class invidious_embed{ this.player_iframe = document.createElement("iframe"); this.loaded = false; this.addEventListener('loadedmetadata',()=>{this.event_executor('ready');this.loaded=true}); + this.addEventListener('loadedmetadata',()=>{this.setVolume(this.volume)}); this.player_iframe.src = iframe_src; if(options.width!==undefined&&typeof options.width==='number'){ this.player_iframe.width = options.width; @@ -98,7 +101,6 @@ class invidious_embed{ this.player_iframe.height = this.player_iframe.width * (9/16); } this.player_iframe.style.border = "none"; - this.eventobject = {ready:[],ended:[],error:[],ratechange:[],volumechange:[],waiting:[],timeupdate:[],loadedmetadata:[],play:[],seeking:[],seeked:[],playerresize:[],pause:[],statechange:[]}; replace_elemnt.replaceWith(this.player_iframe); this.eventdata = {}; return this; @@ -198,6 +200,7 @@ class invidious_embed{ } setVolume(volume){ volume = Number(volume); + this.volume = volume; if(volume!==NaN&&volume!=undefined&&volume>=0&&volume<=100){ this.postMessage({eventname:'setvolume',value:volume/100}); } @@ -276,7 +279,7 @@ class invidious_embed{ startSeconds = option.startSeconds; } if(option.endSeconds!==undefined&&typeof option.endSeconds==='number'&&option.endSeconds>=0){ - startSeconds = option.endSeconds; + endSeconds = option.endSeconds; } } if(mediaContetUrl.length>0){ @@ -355,9 +358,6 @@ class invidious_embed{ getVideoUrl(){ return this.target_origin + "/watch?v=" + this.videoId; } - getVideoTitle(){//original function - return this.promise_send_event('gettitle'); - } async getVideoEmbedCode(){ var title = await this.getVideoTitle(); return ''; @@ -370,6 +370,9 @@ class invidious_embed{ window.addEventListener('message',(ms)=>{this.receiveMessage(ms)}); this.message_wait = {getvolume:[],getmutestatus:[],getduration:[],getcurrenttime:[],getplaybackrate:[],getavailableplaybackrates:[],gettitle:[]}; } + async getVideoData(){ + return {video_id:this.videoId,title:this.promise_send_event('gettitle')}; + } } function invidious_ready(func){ if(typeof func==='function'){ From 2ae3b3e2e4206a16a82a7dda81fdbde1db58402c Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Sat, 26 Aug 2023 08:31:54 +0900 Subject: [PATCH 06/27] fix getVideoData title not correct answer --- assets/js/invidious_iframe_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index d08829f1..aa1d7b23 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -371,7 +371,7 @@ class invidious_embed{ this.message_wait = {getvolume:[],getmutestatus:[],getduration:[],getcurrenttime:[],getplaybackrate:[],getavailableplaybackrates:[],gettitle:[]}; } async getVideoData(){ - return {video_id:this.videoId,title:this.promise_send_event('gettitle')}; + return {video_id:this.videoId,title:await this.promise_send_event('gettitle')}; } } function invidious_ready(func){ From bfba48af8166f4a901eb7e909b2ab1a15b89dead Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Tue, 29 Aug 2023 07:45:33 +0900 Subject: [PATCH 07/27] embed.js additional_info more short Co-authored-by: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> --- assets/js/embed.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 1ab8bc5f..1b323194 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -78,8 +78,7 @@ function return_message(message,target_window){ additional_info['value'] = add_value; } if(message.eventname==='error'){ - let add_value = {geterrorcode:player.error().code}; - additional_info['value'] = add_value; + additional_info['value'] = {geterrorcode:player.error().code}; } message = Object.assign(additional_info,message); let target_origin = url_params.get('origin'); From e9faeb793be83c4da9d55c12c2ce4bc3a8c46f85 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Tue, 29 Aug 2023 07:46:30 +0900 Subject: [PATCH 08/27] invidious_iframe_api.js fix typo Co-authored-by: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> --- assets/js/invidious_iframe_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index aa1d7b23..499f0837 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -346,7 +346,7 @@ class invidious_embed{ cueVideoById(option,startSeconds){ this.playOtherVideoById(option,false,startSeconds); } - cuevVideoByUrl(option,startSeconds){ + cueVideoByUrl(option,startSeconds){ this.playOtherVideoById(option,false,startSeconds); } loadVideoByUrl(option,startSeconds){ From 024246d1b3b274dc545b07cd4062cde839a01cbd Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Tue, 29 Aug 2023 07:51:48 +0900 Subject: [PATCH 09/27] embed.js target_origin more short code Co-authored-by: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> --- assets/js/embed.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 1b323194..d058e323 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -81,10 +81,7 @@ function return_message(message,target_window){ additional_info['value'] = {geterrorcode:player.error().code}; } message = Object.assign(additional_info,message); - let target_origin = url_params.get('origin'); - if(target_origin===null){ - target_origin = '*'; - } + let target_origin = url_params.get('origin') || '*'; target_window.postMessage(message,target_origin); } From 4bb9a618be8a8cce01d9172d23164491aca29ae3 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Tue, 29 Aug 2023 07:56:52 +0900 Subject: [PATCH 10/27] error message more easy to understand Co-authored-by: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> --- assets/js/invidious_iframe_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 499f0837..d4f07a8d 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -30,7 +30,7 @@ class invidious_embed{ this.eventobject = {ready:[],ended:[],error:[],ratechange:[],volumechange:[],waiting:[],timeupdate:[],loadedmetadata:[],play:[],seeking:[],seeked:[],playerresize:[],pause:[],statechange:[]}; var replace_elemnt; if(element===undefined||element===null){ - throw 'please set element id or HTMLElement'; + throw 'Please, pass element id or HTMLElement as first argument'; } else if(typeof element==='string'){ replace_elemnt = document.getElementById(element); From 37cd696a4642ecb8666d3c9b2e59bd954a90e5c4 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:22:55 +0900 Subject: [PATCH 11/27] replace var with let Co-authored-by: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> --- assets/js/invidious_iframe_api.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index d4f07a8d..1bd7637d 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -14,7 +14,7 @@ class invidious_embed{ } } removeEventListner(eventname,func){ - var internal_eventname; + let internal_eventname; if(eventname in invidious_embed.eventname_table){ internal_eventname = invidious_embed.eventname_table[eventname]; } @@ -63,7 +63,7 @@ class invidious_embed{ search_params.append('enablejsapi','1'); if(typeof options.playerVars==='object'){ this.option_playerVars = options.playerVars; - for(var x in options.playerVars){ + for (let x in options.playerVars){ if(typeof x==='string'&&typeof options.playerVars[x]==='string'){ search_params.append(x,options.playerVars[x]); } From 26f9e8bf0db80a43b3bb0728cbb80a9f496462e7 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:26:20 +0900 Subject: [PATCH 12/27] improve type check Co-authored-by: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> --- assets/js/invidious_iframe_api.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 1bd7637d..8d035c9f 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -83,7 +83,7 @@ class invidious_embed{ this.addEventListener('loadedmetadata',()=>{this.event_executor('ready');this.loaded=true}); this.addEventListener('loadedmetadata',()=>{this.setVolume(this.volume)}); this.player_iframe.src = iframe_src; - if(options.width!==undefined&&typeof options.width==='number'){ + if (typeof options.width === 'number'){ this.player_iframe.width = options.width; } else{ @@ -139,7 +139,7 @@ class invidious_embed{ this.message_wait[message.data.command] = []; break; case 'event': - if(message.data.eventname!==undefined&&typeof message.data.eventname==='string'){ + if (typeof message.data.eventname === 'string') { this.event_executor(message.data.eventname); var previous_status = this.player_status; switch(message.data.eventname){ @@ -254,7 +254,7 @@ class invidious_embed{ else{ mediaContetUrl = option; } - if(startSeconds_arg!==undefined&&typeof startSeconds_arg==='number'){ + if (typeof startSeconds_arg === 'number') { startSeconds = startSeconds_arg; } } @@ -268,7 +268,7 @@ class invidious_embed{ this.event_executor('error'); } } - else if(option.mediaContentUrl!==undefined&&typeof option.mediaContentUrl==='string'){ + else if (typeof option.mediaContentUrl === 'string') { mediaContetUrl = option.mediaContentUrl; } else{ From 8239d1da5d00641e863b946937e481eca9e8e45e Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:27:32 +0900 Subject: [PATCH 13/27] improve error process Co-authored-by: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> --- assets/js/invidious_iframe_api.js | 1 + 1 file changed, 1 insertion(+) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 8d035c9f..e85a2a53 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -54,6 +54,7 @@ class invidious_embed{ else{ this.error_code = 2; this.event_executor('error'); + return; } var search_params = new URLSearchParams(''); search_params.append('widgetid',invidious_embed.widgetid); From 2134dd89d8a9658bc8cb921a3b009c7f38b037d6 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:40:03 +0900 Subject: [PATCH 14/27] improve return data Co-authored-by: AHOHNMYC <24810600+AHOHNMYC@users.noreply.github.com> --- assets/js/invidious_iframe_api.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index e85a2a53..4690048e 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -310,12 +310,7 @@ class invidious_embed{ search_params.append('widgetid',invidious_embed.widgetid); this.widgetid = invidious_embed.widgetid; invidious_embed.widgetid++; - if(autoplay){ - search_params.append('autoplay',1); - } - else{ - search_params.append('autoplay',0); - } + search_params.append('autoplay', Number(autoplay)); if(this.option_playerVars!==undefined){ for(var x in this.option_playerVars){ if(x!=='autoplay'&&x!=='start'&&x!=='end'){ @@ -361,7 +356,7 @@ class invidious_embed{ } async getVideoEmbedCode(){ var title = await this.getVideoTitle(); - return ''; + return ''; } getCurrentTime(){ return this.promise_send_event('getcurrenttime'); From 6a1beeb78968c0d8a19b9a2dd31f2351dc03e9cf Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Wed, 30 Aug 2023 00:52:58 +0900 Subject: [PATCH 15/27] small improve --- assets/js/embed.js | 139 +++++------ assets/js/invidious_iframe_api.js | 380 ++++++++++++++++-------------- 2 files changed, 278 insertions(+), 241 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index d058e323..9b47ff2a 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -63,116 +63,117 @@ addEventListener('load', function (e) { } }); -function return_message(message,target_window){ - if(target_window===undefined){ +function return_message(message, target_window) { + if (target_window === undefined) { target_window = window.parent; } let url_params = new URLSearchParams(location.search); let widgetid = url_params.get('widgetid'); - let additional_info = {from:'invidious_control'}; - if(widgetid!==null){ + let additional_info = { from: 'invidious_control' }; + if (widgetid !== null) { additional_info.widgetid = widgetid; } - if(message.message_kind==='event'&&message.eventname==='timeupdate'||message.eventname==='loadedmetadata'){ - let add_value = {getvolume:player.volume(),getduration:player.duration(),getcurrenttime:player.currentTime(),getplaystatus:player.paused(),getplaybackrate:player.playbackRate(),getloopstatus:player.loop(),getmutestatus:player.muted(),getfullscreenstatus:player.isFullscreen(),getavailableplaybackrates:options.playbackRates,gettitle:player_data.title}; - additional_info['value'] = add_value; + if (message.message_kind === 'event') { + if (message.eventname === 'timeupdate' || message.eventname === 'loadedmetadata') { + additional_info['value'] = { getvolume: player.volume(), getduration: player.duration(), getcurrenttime: player.currentTime(), getplaystatus: player.paused(), getplaybackrate: player.playbackRate(), getloopstatus: player.loop(), getmutestatus: player.muted(), getfullscreenstatus: player.isFullscreen(), getavailableplaybackrates: options.playbackRates, gettitle: player_data.title }; + } } - if(message.eventname==='error'){ - additional_info['value'] = {geterrorcode:player.error().code}; + if (message.eventname === 'error') { + additional_info['value'] = { geterrorcode: player.error().code }; } - message = Object.assign(additional_info,message); + message = Object.assign(additional_info, message); let target_origin = url_params.get('origin') || '*'; - target_window.postMessage(message,target_origin); + target_window.postMessage(message, target_origin); } -function control_embed_iframe(message){ - let url_params = new URLSearchParams(location.search); - let origin = url_params.get('origin'); - if(origin===null||origin===message.origin){ - let widgetid = url_params.get('widgetid'); - if((widgetid===null&&message.data.widgetid===null)||widgetid===message.data.widgetid&&message.data.target==='invidious_control'){ - switch(message.data.eventname){ +function control_embed_iframe(message) { + const url_params = new URLSearchParams(location.search); + const origin = url_params.get('origin'); + const origin_equal = origin === null || origin === message.origin; + if (origin_equal) { + const widgetid = url_params.get('widgetid'); + const widgetid_equal = (widgetid === null && message.data.widgetid === null) || widgetid === message.data.widgetid; + const target_name_equal = message.data.target === 'invidious_control'; + const eventname_string_check = typeof message.data.eventname === 'string'; + if (widgetid_equal && target_name_equal && eventname_string_check) { + let message_return_value; + switch (message.data.eventname) { case 'play': player.play(); break; case 'pause': player.pause(); break; - case 'getvolume': - return_message({command:'getvolume',value:player.volume(),message_kind:'info_return'},message.source); - break; + case 'setvolume': player.volume(message.data.value); break; - case 'getduration': - return_message({command:'getduration',value:player.duration(),message_kind:'info_return'},message.source); - break; - case 'getcurrenttime': - return_message({command:'getcurrenttime',value:player.currentTime(),message_kind:'info_return'},message.source); - break; case 'seek': const duration = player.duration(); let newTime = helpers.clamp(message.data.value, 0, duration); player.currentTime(newTime); break; - case 'getplaystatus': - return_message({command:'getplaystatus',value:player.paused(),message_kind:'info_return'},message.source); - break; - case 'getplaybackrate': - return_message({command:'getplaybackrate',value:player.playbackRate(),message_kind:'info_return'},message.source); - break; case 'setplaybackrate': player.playbackRate(message.data.value); break; - case 'getavailableplaybackrates': - return_message({command:'getavailableplaybackrates',value:options.playbackRates,message_kind:'info_return'},message.source); - break; - case 'getloopstatus': - return_message({command:'getloopstatus',value:player.loop(),message_kind:'info_return'},message.source); - break; case 'setloopstatus': player.loop(message.data.value); break; - case 'getmutestatus': - return_message({command:'getmutestatus',value:player.muted(),message_kind:'info_return'},message.source); - break; - case 'setmutestatus': - player.muted(message.data.value); - break; - case 'gettitle': - return_message({command:'gettitle',value:player_data.title,message_kind:'info_return'},message.source); - break; - case 'getfullscreenstatus': - return_message({command:'getfullscreenstatus',value:player.isFullscreen(),message_kind:'info_return'},message.source); - break; case 'requestfullscreen': player.requestFullscreen(); break; case 'exitfullscreen': player.exitFullscreen(); break; - case 'geterrorcode': - return_message({command:'geterrorcode',value:player.error().code,message_kind:'info_return'},message.source); - break; - case 'getplaylist': + case 'getvolume': + message_return_value = player.volume(); + break; + case 'getduration': + message_return_value = player.duration(); + break; + case 'getcurrenttime': + message_return_value = player.currentTime(); + break; + case 'getplaystatus': + message_return_value = player.paused(); + break; + case 'getplaybackrate': + message_return_value = player.playbackRate(); + break; + case 'getavailableplaybackrates': + message_return_value = options.playbackRates; + break; + case 'getloopstatus': + message_return_value = player.loop(); + break; + case 'getmutestatus': + message_return_value = player.muted(); + break; + case 'gettitle': + message_return_value = player_data.title; + break; + case 'getfullscreenstatus': + message_return_value = player.isFullscreen(); + break; + case 'geterrorcode': + message_return_value = player.error().code; + break; + default: + console.info("Unhandled event name: " + message.data.eventname); + break; + } + if (message_return_value !== undefined) { + return_message({ command: message.data.eventname, value: message_return_value, message_kind: 'info_return' }, message.source); } } } } -if(new URLSearchParams(location.search).get('enablejsapi')==='1'){ - window.addEventListener('message',control_embed_iframe); - player.on('ended',function(){return_message({message_kind:'event',eventname:'ended'})}); - player.on('error',function(){return_message({message_kind:'event',eventname:'error'})}); - player.on('ratechange',function(){return_message({message_kind:'event',eventname:'ratechange'})}); - player.on('volumechange',function(){return_message({message_kind:'event',eventname:'volumechange'})}); - player.on('waiting',function(){return_message({message_kind:'event',eventname:'waiting'})}); - player.on('timeupdate',function(){return_message({message_kind:'event',eventname:'timeupdate'})}); - player.on('loadedmetadata',function(){return_message({message_kind:'event',eventname:'loadedmetadata'})}); - player.on('play',function(){return_message({message_kind:'event',eventname:'play'})}); - player.on('seeking',function(){return_message({message_kind:'event',eventname:'seeking'})}); - player.on('seeked',function(){return_message({message_kind:'event',eventname:'seeked'})}); - player.on('playerresize',function(){return_message({message_kind:'event',eventname:'playerresize'})}); - player.on('pause',function(){return_message({message_kind:'event',eventname:'pause'})}); +if (new URLSearchParams(location.search).get('enablejsapi') === '1') { + window.addEventListener('message', control_embed_iframe); + const event_list = ['ended', 'error', 'ratechange', 'volumechange', 'waiting', 'timeupdate', 'loadedmetadata', 'play', 'seeking', 'seeked', 'playerresize', 'pause']; + event_list.forEach(event_name => { + player.on(event_name, function () { return_message({ message_kind: 'event', eventname: event_name }) }); + }); } diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 4690048e..48d3b978 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -1,149 +1,156 @@ -class invidious_embed{ +class invidious_embed { static widgetid = 0; - static eventname_table = {onPlaybackRateChange:'ratechange',onStateChange:'statechange',onerror:'error'}; + static eventname_table = { onPlaybackRateChange: 'ratechange', onStateChange: 'statechange', onerror: 'error' }; + static available_event_name = ['ended', 'error', 'ratechange', 'volumechange', 'waiting', 'timeupdate', 'loadedmetadata', 'play', 'seeking', 'seeked', 'playerresize', 'pause']; static api_promise = false; - addEventListener(eventname,func){ - if(eventname in invidious_embed.eventname_table){ + static invidious_instance = ''; + addEventListener(eventname, func) { + if (eventname in invidious_embed.eventname_table) { this.eventobject[invidious_embed.eventname_table[eventname]].push(func); } - else{ - try{ - this.eventobject[eventname].push(func); - } - catch{} + else { + this.eventobject[eventname].push(func); } } - removeEventListner(eventname,func){ + + removeEventListner(eventname, func) { let internal_eventname; - if(eventname in invidious_embed.eventname_table){ + if (eventname in invidious_embed.eventname_table) { internal_eventname = invidious_embed.eventname_table[eventname]; } - else{ + else if (invidious_embed.available_event_name.includes(eventname)) { internal_eventname = eventname; } - this.eventobject[internal_eventname] = this.eventobject[internal_eventname].fillter(x=>x!==func); + this.eventobject[internal_eventname] = this.eventobject[internal_eventname].fillter(x => x !== func); } - Player(element,options){ + + Player(element, options) { this.player_status = -1; this.error_code = 0; this.volume = 100; - this.eventobject = {ready:[],ended:[],error:[],ratechange:[],volumechange:[],waiting:[],timeupdate:[],loadedmetadata:[],play:[],seeking:[],seeked:[],playerresize:[],pause:[],statechange:[]}; - var replace_elemnt; - if(element===undefined||element===null){ + this.eventobject = { ready: [], ended: [], error: [], ratechange: [], volumechange: [], waiting: [], timeupdate: [], loadedmetadata: [], play: [], seeking: [], seeked: [], playerresize: [], pause: [], statechange: [] }; + let replace_elemnt; + if (element === undefined || element === null) { throw 'Please, pass element id or HTMLElement as first argument'; } - else if(typeof element==='string'){ + else if (typeof element === 'string') { replace_elemnt = document.getElementById(element); } - else{ + else { replace_elemnt = element; } - var iframe_src = ''; - if(options.host!==undefined&&options.host!==""){ + let iframe_src = ''; + if (options.host !== undefined && options.host !== "") { iframe_src = new URL(options.host).origin; } - else{ + else if (invidious_embed.invidious_instance !== '') { + iframe_src = invidious_embed.invidious_instance; + } + else { iframe_src = 'https://vid.puffyan.us';//I set most hot instanse but this may need discuss or change ay to default instanse } this.target_origin = iframe_src.slice(); iframe_src += '/embed/'; - if(typeof options.videoId==='string'&&options.videoId.length===11){ + if (typeof options.videoId === 'string' && options.videoId.length === 11) { iframe_src += options.videoId; this.videoId = options.videoId; } - else{ + else { this.error_code = 2; this.event_executor('error'); return; } - var search_params = new URLSearchParams(''); - search_params.append('widgetid',invidious_embed.widgetid); + let search_params = new URLSearchParams(''); + search_params.append('widgetid', invidious_embed.widgetid); this.widgetid = invidious_embed.widgetid; invidious_embed.widgetid++; - search_params.append('origin',location.origin); - search_params.append('enablejsapi','1'); - if(typeof options.playerVars==='object'){ + search_params.append('origin', location.origin); + search_params.append('enablejsapi', '1'); + if (typeof options.playerVars === 'object') { this.option_playerVars = options.playerVars; - for (let x in options.playerVars){ - if(typeof x==='string'&&typeof options.playerVars[x]==='string'){ - search_params.append(x,options.playerVars[x]); + for (let x in options.playerVars) { + if (typeof x === 'string' && typeof options.playerVars[x] === 'string') { + search_params.append(x, options.playerVars[x]); } } - if(options.playerVars.autoplay===undefined){ - search_params.append('autoplay','0'); + if (options.playerVars.autoplay === undefined) { + search_params.append('autoplay', '0'); } } iframe_src += "?" + search_params.toString(); - if(options.events!==undefined&&typeof options.events==='object'){ - for(let x in options.events){ - this.addEventListener(x,options.events[x]); + if (typeof options.events === 'object') { + for (let x in options.events) { + this.addEventListener(x, options.events[x]); } } this.player_iframe = document.createElement("iframe"); this.loaded = false; - this.addEventListener('loadedmetadata',()=>{this.event_executor('ready');this.loaded=true}); - this.addEventListener('loadedmetadata',()=>{this.setVolume(this.volume)}); + this.addEventListener('loadedmetadata', () => { this.event_executor('ready'); this.loaded = true }); + this.addEventListener('loadedmetadata', () => { this.setVolume(this.volume) }); this.player_iframe.src = iframe_src; - if (typeof options.width === 'number'){ + if (typeof options.width === 'number') { this.player_iframe.width = options.width; } - else{ - if(document.body.clientWidth < 640){ + else { + if (document.body.clientWidth < 640) { this.player_iframe.width = document.body.clientWidth; } - else{ + else { this.player_iframe.width = 640; } } - if(options.width!==undefined&&typeof options.width==='number'){ + if (typeof options.width === 'number') { this.player_iframe.width = options.width; } - else{ - this.player_iframe.height = this.player_iframe.width * (9/16); + else { + this.player_iframe.height = this.player_iframe.width * (9 / 16); } this.player_iframe.style.border = "none"; replace_elemnt.replaceWith(this.player_iframe); this.eventdata = {}; return this; } - postMessage(data){ - var additionalInfo = {'origin':location.origin,'widgetid':String(this.widgetid),'target':'invidious_control'}; - data = Object.assign(additionalInfo,data); - this.player_iframe.contentWindow.postMessage(data,this.target_origin); + + postMessage(data) { + const additionalInfo = { 'origin': location.origin, 'widgetid': this.widgetid.toString(), 'target': 'invidious_control' }; + data = Object.assign(additionalInfo, data); + this.player_iframe.contentWindow.postMessage(data, this.target_origin); } - event_executor(eventname){ - var execute_functions = this.eventobject[eventname]; - var return_data = {data:undefined,target:this}; - if(eventname==='statechange'){ + + event_executor(eventname) { + const execute_functions = this.eventobject[eventname]; + let return_data = { data: undefined, target: this }; + if (eventname === 'statechange') { return_data.data = this.getPlayerState(); } - for(var x=0;x { + if (message.data.command === 'getvolume') { + element(message.data.value * 100); } - else{ - promise_array[x](message.data.value); + else { + element(message.data.value); } - } + }); this.message_wait[message.data.command] = []; break; case 'event': if (typeof message.data.eventname === 'string') { this.event_executor(message.data.eventname); - var previous_status = this.player_status; - switch(message.data.eventname){ + const previous_status = this.player_status; + switch (message.data.eventname) { case 'ended': this.player_status = 0; break; @@ -152,7 +159,7 @@ class invidious_embed{ break; case 'timeupdate': this.player_status = 1; - this.eventdata = Object.assign({},this.eventdata,message.data.value); + this.eventdata = Object.assign({}, this.eventdata, message.data.value); break; case 'pause': this.player_status = 2; @@ -161,222 +168,251 @@ class invidious_embed{ this.player_status = 3; break; case 'loadedmetadata': - this.eventdata = Object.assign({},this.eventdata,message.data.value); + this.eventdata = Object.assign({}, this.eventdata, message.data.value); break; } - if(previous_status!==this.player_status){ + if (previous_status !== this.player_status) { this.event_executor('statechange'); } } } } } - promise_resolve(){ - var res_outer; - var pro = new Promise((res,rej)=>{res_outer=res}); - return {'promise':pro,'resolve':res_outer}; - } - promise_send_event(event_name){ - if(invidious_embed.api_promise){ - var pro_object = this.promise_resolve(); - this.message_wait[event_name].push(pro_object.resolve); - this.postMessage({eventname:event_name}); - return pro_object.promise; + + promise_send_event(event_name) { + if (invidious_embed.api_promise) { + const promise_object = new Promise((resolve, reject) => { this.message_wait[event_name].push(resolve) }); + this.postMessage({ eventname: event_name }); + return promise_object; } - else{ + else { return this.eventdata[event_name]; } } - getPlayerState(){ + + getPlayerState() { return this.player_status; } - playVideo(){ - this.postMessage({eventname:'play'}); + + playVideo() { + this.postMessage({ eventname: 'play' }); } - pauseVideo(){ - this.postMessage({eventname:'pause'}); + + pauseVideo() { + this.postMessage({ eventname: 'pause' }); } - getVolume(){ + + getVolume() { return this.promise_send_event('getvolume'); } - setVolume(volume){ - volume = Number(volume); - this.volume = volume; - if(volume!==NaN&&volume!=undefined&&volume>=0&&volume<=100){ - this.postMessage({eventname:'setvolume',value:volume/100}); + + setVolume(volume) { + if (typeof volume === 'number') { + this.volume = volume; + if (volume !== NaN && volume != undefined && volume >= 0 && volume <= 100) { + this.postMessage({ eventname: 'setvolume', value: volume / 100 }); + } + } + else { + console.warn("setVolume first argument must be number"); } } - getIframe(){ + + getIframe() { return this.player_iframe; } - destroy(){ + + destroy() { this.player_iframe.remove(); } - mute(){ - this.postMessage({eventname:'setmutestatus',value:true}); + + mute() { + this.postMessage({ eventname: 'setmutestatus', value: true }); } - unMute(){ - this.postMessage({eventname:'setmutestatus',value:false}); + + unMute() { + this.postMessage({ eventname: 'setmutestatus', value: false }); } - isMuted(){ + + isMuted() { return this.promise_send_event('getmutestatus'); } - seekTo(seconds,allowSeekAhead){//seconds must be a number and allowSeekAhead is ignore + + seekTo(seconds, allowSeekAhead) {//seconds must be a number and allowSeekAhead is ignore seconds = Number(seconds); - if(seconds!==NaN&&seconds!==undefined){ - this.postMessage({eventname:'seek',value:seconds}); + if (seconds !== NaN && seconds !== undefined) { + this.postMessage({ eventname: 'seek', value: seconds }); } } - setSize(width,height){//width and height must be Number + + setSize(width, height) {//width and height must be Number this.player_iframe.width = Number(width); this.player_iframe.height = Number(height); } - getPlaybackRate(){ + + getPlaybackRate() { return this.promise_send_event('getplaybackrate'); } - setPlaybackRate(suggestedRate){//suggestedRate must be number.this player allow not available playback rate such as 1.4 + + setPlaybackRate(suggestedRate) {//suggestedRate must be number.this player allow not available playback rate such as 1.4 suggestedRate = Number(suggestedRate); - if(suggestedRate!==NaN&&suggestedRate!==undefined){ - this.postMessage({eventname:'setplaybackrate',value:suggestedRate}); + if (suggestedRate !== NaN && suggestedRate !== undefined) { + this.postMessage({ eventname: 'setplaybackrate', value: suggestedRate }); } } - getAvailablePlaybackRates(){ + + getAvailablePlaybackRates() { return this.promise_send_event('getavailableplaybackrates'); } - playOtherVideoById(option,autoplay,startSeconds_arg){//internal fuction + + playOtherVideoById(option, autoplay, startSeconds_arg) {//internal fuction let videoId = ''; - let startSeconds = -1; + let startSeconds = 0; let endSeconds = -1; let mediaContetUrl = ''; - if(typeof option==='string'){ - if(option.length===11){ + if (typeof option === 'string') { + if (option.length === 11) { videoId = option } - else{ + else { mediaContetUrl = option; } if (typeof startSeconds_arg === 'number') { startSeconds = startSeconds_arg; } } - else if(typeof option==='object'){ - if(option.videoId!==undefined&&typeof option.videoId==='string'){ - if(option.videoId.length==11){ + else if (typeof option === 'object') { + if (typeof option.videoId === 'string') { + if (option.videoId.length == 11) { videoId = option.videoId; } - else{ + else { this.error_code = 2; this.event_executor('error'); + return; } } else if (typeof option.mediaContentUrl === 'string') { mediaContetUrl = option.mediaContentUrl; } - else{ + else { this.error_code = 2; this.event_executor('error'); + return; } - if(option.startSeconds!==undefined&&typeof option.startSeconds==='number'&&option.startSeconds>=0){ + if (typeof option.startSeconds === 'number' && option.startSeconds >= 0) { startSeconds = option.startSeconds; } - if(option.endSeconds!==undefined&&typeof option.endSeconds==='number'&&option.endSeconds>=0){ + if (typeof option.endSeconds === 'number' && option.endSeconds >= 0) { endSeconds = option.endSeconds; } } - if(mediaContetUrl.length>0){ + if (mediaContetUrl.length > 0) { var tmp_videoId = ''; - if(mediaContetUrl.indexOf('/v/')!==-1){ - var end_pos = mediaContetUrl.length-1; - if(mediaContetUrl.indexOf('?')!==-1){ + if (mediaContetUrl.indexOf('/v/') !== -1) { + var end_pos = mediaContetUrl.length - 1; + if (mediaContetUrl.indexOf('?') !== -1) { end_pos = mediaContetUrl.indexOf('?'); } - tmp_videoId = mediaContetUrl.substring(mediaContetUrl.indexOf('/v/'),end_pos); + tmp_videoId = mediaContetUrl.substring(mediaContetUrl.indexOf('/v/'), end_pos); } - else{ + else { tmp_videoId = new URL(mediaContetUrl).searchParams.get('v'); } - if(tmp_videoId===null||tmp_videoId.length!==11){ + if (tmp_videoId === null || tmp_videoId.length !== 11) { this.error_code = 2; this.event_executor('error'); + return; } videoId = tmp_videoId; } - var iframe_sorce = this.target_origin.slice(); + let iframe_sorce = this.target_origin.slice(); iframe_sorce += "/embed/" + videoId; this.videoId = videoId; - var search_params = new URLSearchParams(''); - search_params.append('origin',location.origin); - search_params.append('enablejsapi','1'); - search_params.append('widgetid',invidious_embed.widgetid); + let search_params = new URLSearchParams(''); + search_params.append('origin', location.origin); + search_params.append('enablejsapi', '1'); + search_params.append('widgetid', invidious_embed.widgetid); this.widgetid = invidious_embed.widgetid; invidious_embed.widgetid++; search_params.append('autoplay', Number(autoplay)); - if(this.option_playerVars!==undefined){ - for(var x in this.option_playerVars){ - if(x!=='autoplay'&&x!=='start'&&x!=='end'){ - search_params.append(x,this.option_playerVars[x]); + if (this.option_playerVars !== undefined) { + Object.keys(this.option_playerVars).forEach(key => { + if (key !== 'autoplay' && key !== 'start' && key !== 'end') { + search_params.append(key, this.option_playerVars[key]); } - } + }) } - if(startSeconds!==-1&&startSeconds>=0){ - search_params.append('start',startSeconds); + if (startSeconds > 0) { + search_params.append('start', startSeconds); } - if(endSeconds!==-1&&endSeconds>=0){ - if(endSeconds>startSeconds){ - search_params.append('end',endSeconds); + if (endSeconds !== -1 && endSeconds >= 0) { + if (endSeconds > startSeconds) { + search_params.append('end', endSeconds); } - else{ - throw 'invalid end seconds'; + else { + throw 'Invalid end seconds because end seconds before start seconds'; } } iframe_sorce += "?" + search_params.toString(); this.player_iframe.src = iframe_sorce; - if(autoplay){ + if (autoplay) { this.player_status = 5; } this.eventdata = {}; } - loadVideoById(option,startSeconds){ - this.playOtherVideoById(option,true,startSeconds); + + loadVideoById(option, startSeconds) { + this.playOtherVideoById(option, true, startSeconds); } - cueVideoById(option,startSeconds){ - this.playOtherVideoById(option,false,startSeconds); + + cueVideoById(option, startSeconds) { + this.playOtherVideoById(option, false, startSeconds); } - cueVideoByUrl(option,startSeconds){ - this.playOtherVideoById(option,false,startSeconds); + + cueVideoByUrl(option, startSeconds) { + this.playOtherVideoById(option, false, startSeconds); } - loadVideoByUrl(option,startSeconds){ - this.playOtherVideoById(option,true,startSeconds); + + loadVideoByUrl(option, startSeconds) { + this.playOtherVideoById(option, true, startSeconds); } - getDuration(){ + + getDuration() { return this.promise_send_event('getduration'); } - getVideoUrl(){ + + getVideoUrl() { return this.target_origin + "/watch?v=" + this.videoId; } - async getVideoEmbedCode(){ + + async getVideoEmbedCode() { var title = await this.getVideoTitle(); - return ''; + return ''; } - getCurrentTime(){ + + getCurrentTime() { return this.promise_send_event('getcurrenttime'); } - constructor(element,options){ - this.Player(element,options); - window.addEventListener('message',(ms)=>{this.receiveMessage(ms)}); - this.message_wait = {getvolume:[],getmutestatus:[],getduration:[],getcurrenttime:[],getplaybackrate:[],getavailableplaybackrates:[],gettitle:[]}; + + constructor(element, options) { + this.Player(element, options); + window.addEventListener('message', (ms) => { this.receiveMessage(ms) }); + this.message_wait = { getvolume: [], getmutestatus: [], getduration: [], getcurrenttime: [], getplaybackrate: [], getavailableplaybackrates: [], gettitle: [] }; } - async getVideoData(){ - return {video_id:this.videoId,title:await this.promise_send_event('gettitle')}; + + async getVideoData() { + return { video_id: this.videoId, title: await this.promise_send_event('gettitle') }; } } -function invidious_ready(func){ - if(typeof func==='function'){ +function invidious_ready(func) { + if (typeof func === 'function') { func(); } } -const invidious = {Player:invidious_embed,PlayerState:{ENDED:0,PLAYING:1,PAUSED:2,BUFFERING:3,CUED:5},ready:invidious_ready}; -try{ +invidious_embed.invidious_instance = new URL(document.currentScript.src).origin; +const invidious = { Player: invidious_embed, PlayerState: { ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3, CUED: 5 }, ready: invidious_ready }; +try { onInvidiousIframeAPIReady(); } -catch{} +catch { } From 1d67e13d9cde66da2d93dc75f67d4373dbaeb40d Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Sun, 3 Sep 2023 02:17:14 +0900 Subject: [PATCH 16/27] improve feature --- assets/js/embed.js | 10 +- assets/js/invidious_iframe_api.js | 254 ++++++++++++++++++++++++------ 2 files changed, 211 insertions(+), 53 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 9b47ff2a..82118050 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -75,7 +75,7 @@ function return_message(message, target_window) { } if (message.message_kind === 'event') { if (message.eventname === 'timeupdate' || message.eventname === 'loadedmetadata') { - additional_info['value'] = { getvolume: player.volume(), getduration: player.duration(), getcurrenttime: player.currentTime(), getplaystatus: player.paused(), getplaybackrate: player.playbackRate(), getloopstatus: player.loop(), getmutestatus: player.muted(), getfullscreenstatus: player.isFullscreen(), getavailableplaybackrates: options.playbackRates, gettitle: player_data.title }; + additional_info['value'] = { getvolume: player.volume(), getduration: player.duration(), getcurrenttime: player.currentTime(), getplaystatus: player.paused(), getplaybackrate: player.playbackRate(), getloopstatus: player.loop(), getmutestatus: player.muted(), getfullscreenstatus: player.isFullscreen(), getavailableplaybackrates: options.playbackRates, gettitle: player_data.title, getplaylistindex: video_data.index, getplaylistid: video_data.plid}; } } if (message.eventname === 'error') { @@ -92,7 +92,7 @@ function control_embed_iframe(message) { const origin_equal = origin === null || origin === message.origin; if (origin_equal) { const widgetid = url_params.get('widgetid'); - const widgetid_equal = (widgetid === null && message.data.widgetid === null) || widgetid === message.data.widgetid; + const widgetid_equal = widgetid === message.data.widgetid; const target_name_equal = message.data.target === 'invidious_control'; const eventname_string_check = typeof message.data.eventname === 'string'; if (widgetid_equal && target_name_equal && eventname_string_check) { @@ -159,6 +159,12 @@ function control_embed_iframe(message) { case 'geterrorcode': message_return_value = player.error().code; break; + case 'getplaylistindex': + message_return_value = video_data.index; + break; + case 'getplaylistid': + message_return_value = video_data.plid; + break; default: console.info("Unhandled event name: " + message.data.eventname); break; diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 48d3b978..fdc10729 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -1,30 +1,147 @@ class invidious_embed { static widgetid = 0; - static eventname_table = { onPlaybackRateChange: 'ratechange', onStateChange: 'statechange', onerror: 'error' }; - static available_event_name = ['ended', 'error', 'ratechange', 'volumechange', 'waiting', 'timeupdate', 'loadedmetadata', 'play', 'seeking', 'seeked', 'playerresize', 'pause']; + static eventname_table = { onPlaybackRateChange: 'ratechange', onStateChange: 'statechange', onError: 'error' ,onReady: 'ready'}; + static available_event_name = ['ready', 'ended', 'error', 'ratechange', 'volumechange', 'waiting', 'timeupdate', 'loadedmetadata', 'play', 'seeking', 'seeked', 'playerresize', 'pause']; static api_promise = false; static invidious_instance = ''; + static api_instance_list = []; + static instance_status_list = {}; + addEventListener(eventname, func) { - if (eventname in invidious_embed.eventname_table) { - this.eventobject[invidious_embed.eventname_table[eventname]].push(func); + if (typeof func === 'function') { + if (eventname in invidious_embed.eventname_table) { + this.eventobject[invidious_embed.eventname_table[eventname]].push(func); + } + else if (invidious_embed.available_event_name.includes(eventname)) { + this.eventobject[eventname].push(func); + } + else { + console.warn('addEventListener cannot find such eventname : ' + eventname); + } } else { - this.eventobject[eventname].push(func); + console.warn("addEventListner secound args must be function"); } } - removeEventListner(eventname, func) { - let internal_eventname; - if (eventname in invidious_embed.eventname_table) { - internal_eventname = invidious_embed.eventname_table[eventname]; + removeEventListener(eventname, func) { + if (typeof func === 'function') { + let internal_eventname; + if (eventname in invidious_embed.eventname_table) { + internal_eventname = invidious_embed.eventname_table[eventname]; + } + else if (invidious_embed.available_event_name.includes(eventname)) { + internal_eventname = eventname; + } + else { + console.warn('removeEventListner cannot find such eventname : ' + eventname); + return; + } + this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(x => { + const arrowFunctionDetected = x.toString()[0] === '('; + if (arrowFunctionDetected) { + x.toString() !== func.toString(); + } + else{ + x !== func; + } + }); } - else if (invidious_embed.available_event_name.includes(eventname)) { - internal_eventname = eventname; + else { + console.warn("removeEventListener secound args must be function"); } - this.eventobject[internal_eventname] = this.eventobject[internal_eventname].fillter(x => x !== func); } - Player(element, options) { + async instance_access_check(instance_origin) { + let return_status; + const status_cahce_exist = instance_origin in invidious_embed.instance_status_list; + if (!status_cahce_exist) { + try{ + const instance_stats = await fetch(instance_origin + '/api/v1/stats'); + if (instance_stats.ok) { + const instance_stats_json = await instance_stats.json(); + if (instance_stats_json.software.name === 'invidious') { + return_status = true; + } + else { + return_status = false; + } + } + else { + return_status = false; + } + } + catch{ + return_status = false; + } + invidious_embed.instance_status_list[instance_origin] = return_status; + return return_status; + } + else { + return invidious_embed.instance_status_list[instance_origin]; + } + } + + async get_instance_list() { + invidious_embed.api_instance_list = []; + const instance_list_api = await (await fetch('https://api.invidious.io/instances.json?pretty=1&sort_by=type,users')).json(); + instance_list_api.forEach(instance_data => { + const http_check = instance_data[1]['type'] === 'https'; + let status_check_api_data; + if (instance_data[1]['monitor'] !== null) { + status_check_api_data = instance_data[1]['monitor']['statusClass'] === 'success'; + } + const api_available = instance_data[1]['api'] && instance_data[1]['cors']; + if (http_check && status_check_api_data && api_available) { + invidious_embed.api_instance_list.push(instance_data[1]['uri']); + } + }); + } + + async auto_instance_select(){ + if (await this.instance_access_check(invidious_embed.invidious_instance)){ + return; + } + else{ + if (invidious_embed.api_instance_list.length === 0) { + await this.get_instance_list(); + } + for (let x=0;x { + if(await this.instance_access_check(instance_origin)){ + invidious_embed.invidious_instance = instance_origin; + return; + } + }); + */ + } + } + + async videoid_accessable_check(videoid){ + const video_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/videos/" + videoid); + return video_api_response.ok; + } + + async getPlaylistVideoids(playlistid) { + const playlist_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/playlists/" + playlistid); + if (playlist_api_response.ok){ + const playlist_api_json = await playlist_api_response.json(); + let tmp_videoid_list = []; + playlist_api_json.videos.forEach(videodata=>tmp_videoid_list.push(videodata.videoId)); + return tmp_videoid_list; + } + else{ + return []; + } + } + + async Player(element, options) { this.player_status = -1; this.error_code = 0; this.volume = 100; @@ -46,14 +163,21 @@ class invidious_embed { else if (invidious_embed.invidious_instance !== '') { iframe_src = invidious_embed.invidious_instance; } - else { - iframe_src = 'https://vid.puffyan.us';//I set most hot instanse but this may need discuss or change ay to default instanse + if (!await this.instance_access_check(iframe_src)) { + await this.auto_instance_select(); + iframe_src = invidious_embed.invidious_instance; } - this.target_origin = iframe_src.slice(); + invidious_embed.invidious_instance = iframe_src; + this.target_origin = iframe_src; iframe_src += '/embed/'; if (typeof options.videoId === 'string' && options.videoId.length === 11) { iframe_src += options.videoId; this.videoId = options.videoId; + if (!await this.videoid_accessable_check(options.videoId)) { + this.error_code = 100; + this.event_executor('error'); + return; + } } else { this.error_code = 2; @@ -85,8 +209,9 @@ class invidious_embed { } this.player_iframe = document.createElement("iframe"); this.loaded = false; - this.addEventListener('loadedmetadata', () => { this.event_executor('ready'); this.loaded = true }); - this.addEventListener('loadedmetadata', () => { this.setVolume(this.volume) }); + this.addEventListener('loadedmetadata', () => { this.event_executor('ready'); this.loaded = true; }); + this.addEventListener('loadedmetadata', () => { this.setVolume(this.volume); }); + this.addEventListener('loadedmetadata', async () => { const plid = await this.promise_send_event('getplaylistid'); (plid === null || plid === undefined) ? this.playlistVideoIds = [] : this.playlistVideoIds = await this.getPlaylistVideoids(plid); }) this.player_iframe.src = iframe_src; if (typeof options.width === 'number') { this.player_iframe.width = options.width; @@ -123,12 +248,14 @@ class invidious_embed { if (eventname === 'statechange') { return_data.data = this.getPlayerState(); } - for (let x = 0; x < execute_functions.length; x++) { - try { - execute_functions[x](return_data); + execute_functions.forEach(func => { + try{ + func(return_data); } - catch { } - } + catch(e){ + console.error(e); + } + }); } receiveMessage(message) { @@ -239,15 +366,24 @@ class invidious_embed { } seekTo(seconds, allowSeekAhead) {//seconds must be a number and allowSeekAhead is ignore - seconds = Number(seconds); - if (seconds !== NaN && seconds !== undefined) { - this.postMessage({ eventname: 'seek', value: seconds }); + if (typeof seconds === 'number') { + if (seconds !== NaN && seconds !== undefined) { + this.postMessage({ eventname: 'seek', value: seconds }); + } + } + else { + console.warn('seekTo first argument type must be number') } } setSize(width, height) {//width and height must be Number - this.player_iframe.width = Number(width); - this.player_iframe.height = Number(height); + if (typeof width === 'number' && typeof height === 'number'){ + this.player_iframe.width = width; + this.player_iframe.height = height; + } + else { + console.warn('setSize first and secound argument type must be number'); + } } getPlaybackRate() { @@ -255,9 +391,16 @@ class invidious_embed { } setPlaybackRate(suggestedRate) {//suggestedRate must be number.this player allow not available playback rate such as 1.4 - suggestedRate = Number(suggestedRate); - if (suggestedRate !== NaN && suggestedRate !== undefined) { - this.postMessage({ eventname: 'setplaybackrate', value: suggestedRate }); + if (typeof suggestedRate === 'number'){ + if (suggestedRate !== NaN) { + this.postMessage({ eventname: 'setplaybackrate', value: suggestedRate }); + } + else { + console.warn('setPlaybackRate first argument NaN is no valid'); + } + } + else{ + console.warn('setPlaybackRate first argument type must be number'); } } @@ -265,7 +408,7 @@ class invidious_embed { return this.promise_send_event('getavailableplaybackrates'); } - playOtherVideoById(option, autoplay, startSeconds_arg) {//internal fuction + async playOtherVideoById(option, autoplay, startSeconds_arg) {//internal fuction let videoId = ''; let startSeconds = 0; let endSeconds = -1; @@ -308,27 +451,24 @@ class invidious_embed { } } if (mediaContetUrl.length > 0) { - var tmp_videoId = ''; - if (mediaContetUrl.indexOf('/v/') !== -1) { - var end_pos = mediaContetUrl.length - 1; - if (mediaContetUrl.indexOf('?') !== -1) { - end_pos = mediaContetUrl.indexOf('?'); - } - tmp_videoId = mediaContetUrl.substring(mediaContetUrl.indexOf('/v/'), end_pos); + const match_result = mediaContetUrl.match(/\/([A-Za-z0-9]{11})\//); + if (match_result !== null && match_result.length === 2){ + videoId = match_result[1]; } - else { - tmp_videoId = new URL(mediaContetUrl).searchParams.get('v'); - } - if (tmp_videoId === null || tmp_videoId.length !== 11) { + else{ this.error_code = 2; this.event_executor('error'); return; } - videoId = tmp_videoId; } let iframe_sorce = this.target_origin.slice(); iframe_sorce += "/embed/" + videoId; this.videoId = videoId; + if (!await this.videoid_accessable_check(videoId)) { + this.error_code = 100; + this.event_executor('error'); + return; + } let search_params = new URLSearchParams(''); search_params.append('origin', location.origin); search_params.append('enablejsapi', '1'); @@ -387,7 +527,7 @@ class invidious_embed { } async getVideoEmbedCode() { - var title = await this.getVideoTitle(); + const title = await this.getVideoTitle(); return ''; } @@ -395,24 +535,36 @@ class invidious_embed { return this.promise_send_event('getcurrenttime'); } + async getVideoData() { + return { video_id: this.videoId, title: await this.promise_send_event('gettitle'), list:this.promise_send_event('getplaylistid') }; + } + + getPlaylistIndex() { + return this.promise_send_event('getplaylistindex'); + } + + getPlaylist() { + return this.playlistVideoIds !== undefined ? this.playlistVideoIds : []; + } + constructor(element, options) { this.Player(element, options); window.addEventListener('message', (ms) => { this.receiveMessage(ms) }); this.message_wait = { getvolume: [], getmutestatus: [], getduration: [], getcurrenttime: [], getplaybackrate: [], getavailableplaybackrates: [], gettitle: [] }; } - - async getVideoData() { - return { video_id: this.videoId, title: await this.promise_send_event('gettitle') }; - } } function invidious_ready(func) { if (typeof func === 'function') { func(); } + else { + console.warn('invidious.ready first argument must be function'); + } } invidious_embed.invidious_instance = new URL(document.currentScript.src).origin; const invidious = { Player: invidious_embed, PlayerState: { ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3, CUED: 5 }, ready: invidious_ready }; -try { +if (typeof onInvidiousIframeAPIReady === 'function'){ onInvidiousIframeAPIReady(); } -catch { } + +const YT = invidious; From cb27c6a33a475d665e068c6189118f0ec325d4d5 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Sun, 3 Sep 2023 02:27:05 +0900 Subject: [PATCH 17/27] improve readable and fix YT overwrite bug --- assets/js/embed.js | 2 +- assets/js/invidious_iframe_api.js | 145 +++++++++++------------------- 2 files changed, 51 insertions(+), 96 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 82118050..ed0f939f 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -75,7 +75,7 @@ function return_message(message, target_window) { } if (message.message_kind === 'event') { if (message.eventname === 'timeupdate' || message.eventname === 'loadedmetadata') { - additional_info['value'] = { getvolume: player.volume(), getduration: player.duration(), getcurrenttime: player.currentTime(), getplaystatus: player.paused(), getplaybackrate: player.playbackRate(), getloopstatus: player.loop(), getmutestatus: player.muted(), getfullscreenstatus: player.isFullscreen(), getavailableplaybackrates: options.playbackRates, gettitle: player_data.title, getplaylistindex: video_data.index, getplaylistid: video_data.plid}; + additional_info['value'] = { getvolume: player.volume(), getduration: player.duration(), getcurrenttime: player.currentTime(), getplaystatus: player.paused(), getplaybackrate: player.playbackRate(), getloopstatus: player.loop(), getmutestatus: player.muted(), getfullscreenstatus: player.isFullscreen(), getavailableplaybackrates: options.playbackRates, gettitle: player_data.title, getplaylistindex: video_data.index, getplaylistid: video_data.plid }; } } if (message.eventname === 'error') { diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index fdc10729..ec5b9b68 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -1,6 +1,6 @@ class invidious_embed { static widgetid = 0; - static eventname_table = { onPlaybackRateChange: 'ratechange', onStateChange: 'statechange', onError: 'error' ,onReady: 'ready'}; + static eventname_table = { onPlaybackRateChange: 'ratechange', onStateChange: 'statechange', onError: 'error', onReady: 'ready' }; static available_event_name = ['ready', 'ended', 'error', 'ratechange', 'volumechange', 'waiting', 'timeupdate', 'loadedmetadata', 'play', 'seeking', 'seeked', 'playerresize', 'pause']; static api_promise = false; static invidious_instance = ''; @@ -11,15 +11,12 @@ class invidious_embed { if (typeof func === 'function') { if (eventname in invidious_embed.eventname_table) { this.eventobject[invidious_embed.eventname_table[eventname]].push(func); - } - else if (invidious_embed.available_event_name.includes(eventname)) { + } else if (invidious_embed.available_event_name.includes(eventname)) { this.eventobject[eventname].push(func); - } - else { + } else { console.warn('addEventListener cannot find such eventname : ' + eventname); } - } - else { + } else { console.warn("addEventListner secound args must be function"); } } @@ -29,11 +26,9 @@ class invidious_embed { let internal_eventname; if (eventname in invidious_embed.eventname_table) { internal_eventname = invidious_embed.eventname_table[eventname]; - } - else if (invidious_embed.available_event_name.includes(eventname)) { + } else if (invidious_embed.available_event_name.includes(eventname)) { internal_eventname = eventname; - } - else { + } else { console.warn('removeEventListner cannot find such eventname : ' + eventname); return; } @@ -41,13 +36,11 @@ class invidious_embed { const arrowFunctionDetected = x.toString()[0] === '('; if (arrowFunctionDetected) { x.toString() !== func.toString(); - } - else{ + } else { x !== func; } }); - } - else { + } else { console.warn("removeEventListener secound args must be function"); } } @@ -56,28 +49,24 @@ class invidious_embed { let return_status; const status_cahce_exist = instance_origin in invidious_embed.instance_status_list; if (!status_cahce_exist) { - try{ + try { const instance_stats = await fetch(instance_origin + '/api/v1/stats'); if (instance_stats.ok) { const instance_stats_json = await instance_stats.json(); if (instance_stats_json.software.name === 'invidious') { return_status = true; - } - else { + } else { return_status = false; } - } - else { + } else { return_status = false; } - } - catch{ + } catch { return_status = false; } invidious_embed.instance_status_list[instance_origin] = return_status; return return_status; - } - else { + } else { return invidious_embed.instance_status_list[instance_origin]; } } @@ -98,45 +87,35 @@ class invidious_embed { }); } - async auto_instance_select(){ - if (await this.instance_access_check(invidious_embed.invidious_instance)){ + async auto_instance_select() { + if (await this.instance_access_check(invidious_embed.invidious_instance)) { return; - } - else{ + } else { if (invidious_embed.api_instance_list.length === 0) { await this.get_instance_list(); } - for (let x=0;x { - if(await this.instance_access_check(instance_origin)){ - invidious_embed.invidious_instance = instance_origin; - return; - } - }); - */ } } - async videoid_accessable_check(videoid){ + async videoid_accessable_check(videoid) { const video_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/videos/" + videoid); return video_api_response.ok; } async getPlaylistVideoids(playlistid) { const playlist_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/playlists/" + playlistid); - if (playlist_api_response.ok){ + if (playlist_api_response.ok) { const playlist_api_json = await playlist_api_response.json(); let tmp_videoid_list = []; - playlist_api_json.videos.forEach(videodata=>tmp_videoid_list.push(videodata.videoId)); + playlist_api_json.videos.forEach(videodata => tmp_videoid_list.push(videodata.videoId)); return tmp_videoid_list; - } - else{ + } else { return []; } } @@ -149,18 +128,15 @@ class invidious_embed { let replace_elemnt; if (element === undefined || element === null) { throw 'Please, pass element id or HTMLElement as first argument'; - } - else if (typeof element === 'string') { + } else if (typeof element === 'string') { replace_elemnt = document.getElementById(element); - } - else { + } else { replace_elemnt = element; } let iframe_src = ''; if (options.host !== undefined && options.host !== "") { iframe_src = new URL(options.host).origin; - } - else if (invidious_embed.invidious_instance !== '') { + } else if (invidious_embed.invidious_instance !== '') { iframe_src = invidious_embed.invidious_instance; } if (!await this.instance_access_check(iframe_src)) { @@ -178,8 +154,7 @@ class invidious_embed { this.event_executor('error'); return; } - } - else { + } else { this.error_code = 2; this.event_executor('error'); return; @@ -215,19 +190,16 @@ class invidious_embed { this.player_iframe.src = iframe_src; if (typeof options.width === 'number') { this.player_iframe.width = options.width; - } - else { + } else { if (document.body.clientWidth < 640) { this.player_iframe.width = document.body.clientWidth; - } - else { + } else { this.player_iframe.width = 640; } } if (typeof options.width === 'number') { this.player_iframe.width = options.width; - } - else { + } else { this.player_iframe.height = this.player_iframe.width * (9 / 16); } this.player_iframe.style.border = "none"; @@ -249,10 +221,9 @@ class invidious_embed { return_data.data = this.getPlayerState(); } execute_functions.forEach(func => { - try{ + try { func(return_data); - } - catch(e){ + } catch (e) { console.error(e); } }); @@ -266,8 +237,7 @@ class invidious_embed { promise_array.forEach(element => { if (message.data.command === 'getvolume') { element(message.data.value * 100); - } - else { + } else { element(message.data.value); } }); @@ -311,8 +281,7 @@ class invidious_embed { const promise_object = new Promise((resolve, reject) => { this.message_wait[event_name].push(resolve) }); this.postMessage({ eventname: event_name }); return promise_object; - } - else { + } else { return this.eventdata[event_name]; } } @@ -339,8 +308,7 @@ class invidious_embed { if (volume !== NaN && volume != undefined && volume >= 0 && volume <= 100) { this.postMessage({ eventname: 'setvolume', value: volume / 100 }); } - } - else { + } else { console.warn("setVolume first argument must be number"); } } @@ -370,18 +338,16 @@ class invidious_embed { if (seconds !== NaN && seconds !== undefined) { this.postMessage({ eventname: 'seek', value: seconds }); } - } - else { + } else { console.warn('seekTo first argument type must be number') } } setSize(width, height) {//width and height must be Number - if (typeof width === 'number' && typeof height === 'number'){ + if (typeof width === 'number' && typeof height === 'number') { this.player_iframe.width = width; this.player_iframe.height = height; - } - else { + } else { console.warn('setSize first and secound argument type must be number'); } } @@ -391,15 +357,13 @@ class invidious_embed { } setPlaybackRate(suggestedRate) {//suggestedRate must be number.this player allow not available playback rate such as 1.4 - if (typeof suggestedRate === 'number'){ + if (typeof suggestedRate === 'number') { if (suggestedRate !== NaN) { this.postMessage({ eventname: 'setplaybackrate', value: suggestedRate }); - } - else { + } else { console.warn('setPlaybackRate first argument NaN is no valid'); } - } - else{ + } else { console.warn('setPlaybackRate first argument type must be number'); } } @@ -416,29 +380,24 @@ class invidious_embed { if (typeof option === 'string') { if (option.length === 11) { videoId = option - } - else { + } else { mediaContetUrl = option; } if (typeof startSeconds_arg === 'number') { startSeconds = startSeconds_arg; } - } - else if (typeof option === 'object') { + } else if (typeof option === 'object') { if (typeof option.videoId === 'string') { if (option.videoId.length == 11) { videoId = option.videoId; - } - else { + } else { this.error_code = 2; this.event_executor('error'); return; } - } - else if (typeof option.mediaContentUrl === 'string') { + } else if (typeof option.mediaContentUrl === 'string') { mediaContetUrl = option.mediaContentUrl; - } - else { + } else { this.error_code = 2; this.event_executor('error'); return; @@ -452,10 +411,9 @@ class invidious_embed { } if (mediaContetUrl.length > 0) { const match_result = mediaContetUrl.match(/\/([A-Za-z0-9]{11})\//); - if (match_result !== null && match_result.length === 2){ + if (match_result !== null && match_result.length === 2) { videoId = match_result[1]; - } - else{ + } else { this.error_code = 2; this.event_executor('error'); return; @@ -489,8 +447,7 @@ class invidious_embed { if (endSeconds !== -1 && endSeconds >= 0) { if (endSeconds > startSeconds) { search_params.append('end', endSeconds); - } - else { + } else { throw 'Invalid end seconds because end seconds before start seconds'; } } @@ -536,7 +493,7 @@ class invidious_embed { } async getVideoData() { - return { video_id: this.videoId, title: await this.promise_send_event('gettitle'), list:this.promise_send_event('getplaylistid') }; + return { video_id: this.videoId, title: await this.promise_send_event('gettitle'), list: this.promise_send_event('getplaylistid') }; } getPlaylistIndex() { @@ -563,8 +520,6 @@ function invidious_ready(func) { } invidious_embed.invidious_instance = new URL(document.currentScript.src).origin; const invidious = { Player: invidious_embed, PlayerState: { ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3, CUED: 5 }, ready: invidious_ready }; -if (typeof onInvidiousIframeAPIReady === 'function'){ +if (typeof onInvidiousIframeAPIReady === 'function') { onInvidiousIframeAPIReady(); } - -const YT = invidious; From bd35a5a14950f0e0e2e72beddda27a45cd1269c2 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Sun, 3 Sep 2023 09:04:04 +0900 Subject: [PATCH 18/27] fix typo,delete unnecessary type check --- assets/js/invidious_iframe_api.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index ec5b9b68..e6040f13 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -33,8 +33,8 @@ class invidious_embed { return; } this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(x => { - const arrowFunctionDetected = x.toString()[0] === '('; - if (arrowFunctionDetected) { + const allowFunctionDetected = x.toString()[0] === '('; + if (allowFunctionDetected) { x.toString() !== func.toString(); } else { x !== func; @@ -305,7 +305,7 @@ class invidious_embed { setVolume(volume) { if (typeof volume === 'number') { this.volume = volume; - if (volume !== NaN && volume != undefined && volume >= 0 && volume <= 100) { + if (volume !== NaN && volume >= 0 && volume <= 100) { this.postMessage({ eventname: 'setvolume', value: volume / 100 }); } } else { @@ -521,5 +521,9 @@ function invidious_ready(func) { invidious_embed.invidious_instance = new URL(document.currentScript.src).origin; const invidious = { Player: invidious_embed, PlayerState: { ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3, CUED: 5 }, ready: invidious_ready }; if (typeof onInvidiousIframeAPIReady === 'function') { - onInvidiousIframeAPIReady(); + try{ + onInvidiousIframeAPIReady(); + } catch(e) { + console.error(e); + } } From 96439bc15c5764a4fd3d7cc1ab6a9c86d13359cd Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Mon, 4 Sep 2023 00:43:39 +0900 Subject: [PATCH 19/27] improve event process --- assets/js/invidious_iframe_api.js | 52 ++++++------------------------- 1 file changed, 10 insertions(+), 42 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index e6040f13..beb7ec81 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -8,41 +8,17 @@ class invidious_embed { static instance_status_list = {}; addEventListener(eventname, func) { - if (typeof func === 'function') { - if (eventname in invidious_embed.eventname_table) { - this.eventobject[invidious_embed.eventname_table[eventname]].push(func); - } else if (invidious_embed.available_event_name.includes(eventname)) { - this.eventobject[eventname].push(func); - } else { - console.warn('addEventListener cannot find such eventname : ' + eventname); - } - } else { - console.warn("addEventListner secound args must be function"); + if (eventname in invidious_embed.eventname_table) { + eventname = invidious_embed.eventname_table[eventname]; } + this.eventElement.addEventListener(eventname,func); } removeEventListener(eventname, func) { - if (typeof func === 'function') { - let internal_eventname; - if (eventname in invidious_embed.eventname_table) { - internal_eventname = invidious_embed.eventname_table[eventname]; - } else if (invidious_embed.available_event_name.includes(eventname)) { - internal_eventname = eventname; - } else { - console.warn('removeEventListner cannot find such eventname : ' + eventname); - return; - } - this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(x => { - const allowFunctionDetected = x.toString()[0] === '('; - if (allowFunctionDetected) { - x.toString() !== func.toString(); - } else { - x !== func; - } - }); - } else { - console.warn("removeEventListener secound args must be function"); + if (eventname in invidious_embed.eventname_table) { + eventname = invidious_embed.eventname_table[eventname]; } + this.eventElement.removeEventListener(eventname,func); } async instance_access_check(instance_origin) { @@ -121,6 +97,7 @@ class invidious_embed { } async Player(element, options) { + this.eventElement = document.createElement("span"); this.player_status = -1; this.error_code = 0; this.volume = 100; @@ -215,18 +192,7 @@ class invidious_embed { } event_executor(eventname) { - const execute_functions = this.eventobject[eventname]; - let return_data = { data: undefined, target: this }; - if (eventname === 'statechange') { - return_data.data = this.getPlayerState(); - } - execute_functions.forEach(func => { - try { - func(return_data); - } catch (e) { - console.error(e); - } - }); + this.eventElement.dispatchEvent(new Event(eventname)); } receiveMessage(message) { @@ -527,3 +493,5 @@ if (typeof onInvidiousIframeAPIReady === 'function') { console.error(e); } } + +const YT = invidious; From ffb7b7446ea8ddde4fcd4d7dc8d5568ff750145e Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Wed, 6 Sep 2023 23:31:09 +0900 Subject: [PATCH 20/27] support playlist function --- assets/js/embed.js | 3 + assets/js/invidious_iframe_api.js | 192 +++++++++++++++++++++++++++--- 2 files changed, 179 insertions(+), 16 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index ed0f939f..1289effd 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -111,6 +111,9 @@ function control_embed_iframe(message) { case 'seek': const duration = player.duration(); let newTime = helpers.clamp(message.data.value, 0, duration); + if (player.paused() && player.currentTime() === 0) { + player.play(); + } player.currentTime(newTime); break; case 'setplaybackrate': diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index beb7ec81..cbbd0963 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -6,6 +6,7 @@ class invidious_embed { static invidious_instance = ''; static api_instance_list = []; static instance_status_list = {}; + static videodata_cahce = {}; addEventListener(eventname, func) { if (eventname in invidious_embed.eventname_table) { @@ -79,9 +80,21 @@ class invidious_embed { } } + async videodata_api(videoid) { + const not_in_videodata_cahce = !(videoid in invidious_embed.videodata_cahce); + if (not_in_videodata_cahce) { + const video_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/videos/" + videoid + "?fields=title,videoId,paid,premium,isFamilyFriendly,isListed,liveNow"); + if (video_api_response.ok) { + invidious_embed.videodata_cahce[videoid] = Object.assign({},{status:true},await video_api_response.json()); + } else { + invidious_embed.videodata_cahce[videoid] = {status:false}; + } + } + return invidious_embed.videodata_cahce[videoid]; + } + async videoid_accessable_check(videoid) { - const video_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/videos/" + videoid); - return video_api_response.ok; + return (await this.videodata_api(videoid)).status; } async getPlaylistVideoids(playlistid) { @@ -101,6 +114,8 @@ class invidious_embed { this.player_status = -1; this.error_code = 0; this.volume = 100; + this.loop = false; + this.playlistVideoIds = []; this.eventobject = { ready: [], ended: [], error: [], ratechange: [], volumechange: [], waiting: [], timeupdate: [], loadedmetadata: [], play: [], seeking: [], seeked: [], playerresize: [], pause: [], statechange: [] }; let replace_elemnt; if (element === undefined || element === null) { @@ -142,6 +157,7 @@ class invidious_embed { invidious_embed.widgetid++; search_params.append('origin', location.origin); search_params.append('enablejsapi', '1'); + let no_start_parameter = true; if (typeof options.playerVars === 'object') { this.option_playerVars = options.playerVars; for (let x in options.playerVars) { @@ -149,9 +165,17 @@ class invidious_embed { search_params.append(x, options.playerVars[x]); } } + if (options.playerVars.start !== undefined) { + no_start_parameter = false; + } if (options.playerVars.autoplay === undefined) { search_params.append('autoplay', '0'); } + } else { + search_params.append('autoplay', '0'); + } + if (no_start_parameter) { + search_params.append('start', '0'); } iframe_src += "?" + search_params.toString(); if (typeof options.events === 'object') { @@ -163,7 +187,6 @@ class invidious_embed { this.loaded = false; this.addEventListener('loadedmetadata', () => { this.event_executor('ready'); this.loaded = true; }); this.addEventListener('loadedmetadata', () => { this.setVolume(this.volume); }); - this.addEventListener('loadedmetadata', async () => { const plid = await this.promise_send_event('getplaylistid'); (plid === null || plid === undefined) ? this.playlistVideoIds = [] : this.playlistVideoIds = await this.getPlaylistVideoids(plid); }) this.player_iframe.src = iframe_src; if (typeof options.width === 'number') { this.player_iframe.width = options.width; @@ -192,7 +215,13 @@ class invidious_embed { } event_executor(eventname) { - this.eventElement.dispatchEvent(new Event(eventname)); + let event_parameter = {detail:undefined}; + if (eventname === 'statechange') { + event_parameter.detail = this.getPlayerState(); + } else if (eventname === 'error'){ + event_parameter.detail = this.error_code; + } + this.eventElement.dispatchEvent(new Event(eventname,event_parameter)); } receiveMessage(message) { @@ -299,7 +328,7 @@ class invidious_embed { return this.promise_send_event('getmutestatus'); } - seekTo(seconds, allowSeekAhead) {//seconds must be a number and allowSeekAhead is ignore + async seekTo(seconds, allowSeekAhead) {//seconds must be a number and allowSeekAhead is ignore if (typeof seconds === 'number') { if (seconds !== NaN && seconds !== undefined) { this.postMessage({ eventname: 'seek', value: seconds }); @@ -338,7 +367,7 @@ class invidious_embed { return this.promise_send_event('getavailableplaybackrates'); } - async playOtherVideoById(option, autoplay, startSeconds_arg) {//internal fuction + async playOtherVideoById(option, autoplay, startSeconds_arg, additional_argument) {//internal fuction let videoId = ''; let startSeconds = 0; let endSeconds = -1; @@ -401,15 +430,22 @@ class invidious_embed { invidious_embed.widgetid++; search_params.append('autoplay', Number(autoplay)); if (this.option_playerVars !== undefined) { + const ignore_keys = ['autoplay', 'start', 'end', 'index', 'list']; Object.keys(this.option_playerVars).forEach(key => { - if (key !== 'autoplay' && key !== 'start' && key !== 'end') { + if (!ignore_keys.includes(key)) { search_params.append(key, this.option_playerVars[key]); } }) } - if (startSeconds > 0) { - search_params.append('start', startSeconds); + if (typeof additional_argument === 'object') { + const ignore_keys = ['autoplay', 'start', 'end']; + Object.keys(additional_argument).forEach(key => { + if (!ignore_keys.includes(key)) { + search_params.append(key, additional_argument[key]); + } + }) } + search_params.append('start', startSeconds); if (endSeconds !== -1 && endSeconds >= 0) { if (endSeconds > startSeconds) { search_params.append('end', endSeconds); @@ -426,19 +462,134 @@ class invidious_embed { } loadVideoById(option, startSeconds) { - this.playOtherVideoById(option, true, startSeconds); + this.playOtherVideoById(option, true, startSeconds, {}); } cueVideoById(option, startSeconds) { - this.playOtherVideoById(option, false, startSeconds); + this.playOtherVideoById(option, false, startSeconds, {}); } cueVideoByUrl(option, startSeconds) { - this.playOtherVideoById(option, false, startSeconds); + this.playOtherVideoById(option, false, startSeconds, {}); } loadVideoByUrl(option, startSeconds) { - this.playOtherVideoById(option, true, startSeconds); + this.playOtherVideoById(option, true, startSeconds, {}); + } + + async playPlaylist(playlistData,autoplay,index,startSeconds) { + let playlistId; + if (typeof playlistData === 'string') { + this.playlistVideoIds = await this.getPlaylistVideoids(playlistData); + playlistId = playlistData; + } else if (typeof playlistData === 'object') { + if (Array.isArray(playlistData)) { + this.playlistVideoIds = playlistData; + } else { + index = playlistData['index']; + let listType = 'playlist'; + if (typeof playlistData['listType'] === 'string'){ + listType = playlistData['listType']; + } + switch (listType) { + case 'playlist': + if (Array.isArray(playlistData['list'])) { + this.playlistVideoIds = playlistData['list']; + } else if(typeof playlistData['list'] === 'string') { + this.playlistVideoIds = await this.getPlaylistVideoids(playlistData['list']); + playlistId = playlistData['list']; + } else { + console.error('playlist data list must be string or array of strings'); + return; + } + break; + case 'user_uploads': + console.warn('sorry user_uploads not support'); + return; + default: + console.error('listType : ' + listType + ' is unknown'); + return; + } + } + } else { + console.error('playlist function first argument must be string or array of string'); + return; + } + if (this.playlistVideoIds.length === 0){ + console.error('playlist length 0 is invalid'); + return; + } + let parameter = {index:0}; + if (typeof index === 'undefined') { + index = 0; + } else if (typeof index === 'number'){ + parameter.index = index; + } else{ + console.error('index must be number of undefined'); + } + if (typeof playlistId === 'string') { + parameter['list'] = playlistId; + this.playlistId = playlistId; + } + this.playOtherVideoById(this.playlistVideoIds[index],autoplay,startSeconds,parameter); + } + + cuePlaylist(data,index,startSeconds) { + this.playPlaylist(data,false,index,startSeconds); + } + + loadPlaylist(data,index,startSeconds) { + this.playPlaylist(data,true,index,startSeconds); + } + + playVideoAt(index) { + if (typeof index === 'number') { + let parameter = {index:index}; + if (this.playlistId !== undefined) { + parameter['list'] = this.playlistId; + } + this.playOtherVideoById(this.playlistVideoIds[index],true,0,parameter); + } else { + console.error('playVideoAt first argument must be number'); + } + } + + async nextVideo() { + let now_index = this.promise_send_event('getplaylistindex'); + if (now_index === this.playlistVideoIds.length -1) { + if (this.loop) { + now_index = 0; + } else { + console.log('end of playlist'); + return; + } + } else { + now_index++; + } + let parameter = {index:now_index}; + if (this.playlistId !== undefined) { + parameter['list'] = this.playlistId; + } + this.playOtherVideoById(this.playlistVideoIds[now_index],true,0,parameter); + } + + async previousVideo() { + let now_index = this.promise_send_event('getplaylistindex'); + if (now_index === 0) { + if (this.loop) { + now_index = this.playlistVideoIds.length -1; + } else { + console.log('back to start of playlist'); + return; + } + } else { + now_index--; + } + let parameter = {index:now_index}; + if (this.playlistId !== undefined) { + parameter['list'] = this.playlistId; + } + this.playOtherVideoById(this.playlistVideoIds[now_index],true,0,parameter); } getDuration() { @@ -459,7 +610,8 @@ class invidious_embed { } async getVideoData() { - return { video_id: this.videoId, title: await this.promise_send_event('gettitle'), list: this.promise_send_event('getplaylistid') }; + const videoData = await this.videodata_api(this.videoId); + return { video_id: this.videoId, title: await this.promise_send_event('gettitle'), list: await this.promise_send_event('getplaylistid'), isListed:videoData.isListed, isLive:videoData.liveNow, isPremiere:videoData.premium}; } getPlaylistIndex() { @@ -470,12 +622,21 @@ class invidious_embed { return this.playlistVideoIds !== undefined ? this.playlistVideoIds : []; } + setLoop(loopStatus) { + if (typeof loopStatus === 'boolean') { + this.loop = loopStatus; + } else { + console.error('setLoop first argument must be bool'); + } + } + constructor(element, options) { this.Player(element, options); window.addEventListener('message', (ms) => { this.receiveMessage(ms) }); this.message_wait = { getvolume: [], getmutestatus: [], getduration: [], getcurrenttime: [], getplaybackrate: [], getavailableplaybackrates: [], gettitle: [] }; } } + function invidious_ready(func) { if (typeof func === 'function') { func(); @@ -484,6 +645,7 @@ function invidious_ready(func) { console.warn('invidious.ready first argument must be function'); } } + invidious_embed.invidious_instance = new URL(document.currentScript.src).origin; const invidious = { Player: invidious_embed, PlayerState: { ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3, CUED: 5 }, ready: invidious_ready }; if (typeof onInvidiousIframeAPIReady === 'function') { @@ -493,5 +655,3 @@ if (typeof onInvidiousIframeAPIReady === 'function') { console.error(e); } } - -const YT = invidious; From b1a1e5230d6c430d572dd4501f95f69b461e019c Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Wed, 6 Sep 2023 23:54:18 +0900 Subject: [PATCH 21/27] better iframe size and use forEach on playerVars --- assets/js/invidious_iframe_api.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index cbbd0963..eee8d29b 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -160,11 +160,23 @@ class invidious_embed { let no_start_parameter = true; if (typeof options.playerVars === 'object') { this.option_playerVars = options.playerVars; - for (let x in options.playerVars) { - if (typeof x === 'string' && typeof options.playerVars[x] === 'string') { - search_params.append(x, options.playerVars[x]); + Object.keys(options.playerVars).forEach(key=>{ + if (typeof key === 'string') { + let keyValue = options.playerVars[key]; + switch (typeof keyValue) { + case 'number': + keyValue = keyValue.toString(); + break; + case 'string': + break; + default : + console.warn('player vars key value must be string or number'); + } + search_params.append(key, keyValue); + } else { + console.warn('player vars key must be string'); } - } + }) if (options.playerVars.start !== undefined) { no_start_parameter = false; } @@ -191,11 +203,8 @@ class invidious_embed { if (typeof options.width === 'number') { this.player_iframe.width = options.width; } else { - if (document.body.clientWidth < 640) { - this.player_iframe.width = document.body.clientWidth; - } else { - this.player_iframe.width = 640; - } + this.player_iframe.width = 640; + this.player_iframe.style.maxWidth = '100%'; } if (typeof options.width === 'number') { this.player_iframe.width = options.width; @@ -225,7 +234,8 @@ class invidious_embed { } receiveMessage(message) { - if (message.data.from === 'invidious_control' && message.data.widgetid === String(this.widgetid)) { + const onControlAndHasWidgetId = message.data.from==='invidious_control' && message.data.widgetid===this.widgetid.toString(); + if (onControlAndHasWidgetId) { switch (message.data.message_kind) { case 'info_return': const promise_array = this.message_wait[message.data.command]; From c3ea7fb72cf017c73495dc7f024c7b39432ea0a8 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Fri, 8 Sep 2023 00:46:37 +0900 Subject: [PATCH 22/27] fix bugs, rollback event function and readable --- assets/js/embed.js | 8 ++ assets/js/invidious_iframe_api.js | 165 ++++++++++++++++++++---------- 2 files changed, 119 insertions(+), 54 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 1289effd..0a08e24b 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -22,6 +22,8 @@ function get_playlist(plid) { player.on('ended', function () { var url = new URL('https://example.com/embed/' + response.nextVideo); + const search_params = new URLSearchParams(location.search); + url.searchParams.set('list', plid); if (!plid.startsWith('RD')) url.searchParams.set('index', response.index); @@ -33,6 +35,12 @@ function get_playlist(plid) { url.searchParams.set('speed', video_data.params.speed); if (video_data.params.local !== video_data.preferences.local) url.searchParams.set('local', video_data.params.local); + if (search_params.get('widgetid') !== null) + url.searchParams.set('widgetid', search_params.get('widgetid')); + if (search_params.get('origin') !== null) + url.searchParams.set('origin', search_params.get('origin')); + if (search_params.get('enablejsapi') !== null) + url.searchParams.set('enablejsapi', search_params.get('enablejsapi')); location.assign(url.pathname + url.search); }); diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index eee8d29b..2a6e9462 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -9,17 +9,41 @@ class invidious_embed { static videodata_cahce = {}; addEventListener(eventname, func) { - if (eventname in invidious_embed.eventname_table) { - eventname = invidious_embed.eventname_table[eventname]; + if (typeof func === 'function') { + if (eventname in invidious_embed.eventname_table) { + this.eventobject[invidious_embed.eventname_table[eventname]].push(func); + } else if (invidious_embed.available_event_name.includes(eventname)) { + this.eventobject[eventname].push(func); + } else { + console.warn('addEventListener cannot find such eventname : ' + eventname); + } + } else { + console.warn("addEventListner secound args must be function"); } - this.eventElement.addEventListener(eventname,func); } removeEventListener(eventname, func) { - if (eventname in invidious_embed.eventname_table) { - eventname = invidious_embed.eventname_table[eventname]; + if (typeof func === 'function') { + let internal_eventname; + if (eventname in invidious_embed.eventname_table) { + internal_eventname = invidious_embed.eventname_table[eventname]; + } else if (invidious_embed.available_event_name.includes(eventname)) { + internal_eventname = eventname; + } else { + console.warn('removeEventListner cannot find such eventname : ' + eventname); + return; + } + this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(x => { + const allowFunctionDetected = x.toString()[0] === '('; + if (allowFunctionDetected) { + x.toString() !== func.toString(); + } else { + x !== func; + } + }); + } else { + console.warn("removeEventListener secound args must be function"); } - this.eventElement.removeEventListener(eventname,func); } async instance_access_check(instance_origin) { @@ -85,9 +109,9 @@ class invidious_embed { if (not_in_videodata_cahce) { const video_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/videos/" + videoid + "?fields=title,videoId,paid,premium,isFamilyFriendly,isListed,liveNow"); if (video_api_response.ok) { - invidious_embed.videodata_cahce[videoid] = Object.assign({},{status:true},await video_api_response.json()); + invidious_embed.videodata_cahce[videoid] = Object.assign({}, { status: true }, await video_api_response.json()); } else { - invidious_embed.videodata_cahce[videoid] = {status:false}; + invidious_embed.videodata_cahce[videoid] = { status: false }; } } return invidious_embed.videodata_cahce[videoid]; @@ -110,7 +134,6 @@ class invidious_embed { } async Player(element, options) { - this.eventElement = document.createElement("span"); this.player_status = -1; this.error_code = 0; this.volume = 100; @@ -118,6 +141,7 @@ class invidious_embed { this.playlistVideoIds = []; this.eventobject = { ready: [], ended: [], error: [], ratechange: [], volumechange: [], waiting: [], timeupdate: [], loadedmetadata: [], play: [], seeking: [], seeked: [], playerresize: [], pause: [], statechange: [] }; let replace_elemnt; + this.isPlaylistVideoList = false; if (element === undefined || element === null) { throw 'Please, pass element id or HTMLElement as first argument'; } else if (typeof element === 'string') { @@ -160,7 +184,7 @@ class invidious_embed { let no_start_parameter = true; if (typeof options.playerVars === 'object') { this.option_playerVars = options.playerVars; - Object.keys(options.playerVars).forEach(key=>{ + Object.keys(options.playerVars).forEach(key => { if (typeof key === 'string') { let keyValue = options.playerVars[key]; switch (typeof keyValue) { @@ -169,14 +193,14 @@ class invidious_embed { break; case 'string': break; - default : + default: console.warn('player vars key value must be string or number'); } search_params.append(key, keyValue); } else { console.warn('player vars key must be string'); } - }) + }); if (options.playerVars.start !== undefined) { no_start_parameter = false; } @@ -191,14 +215,19 @@ class invidious_embed { } iframe_src += "?" + search_params.toString(); if (typeof options.events === 'object') { - for (let x in options.events) { - this.addEventListener(x, options.events[x]); - } + Object.keys(options.events).forEach(key => { + if (typeof options.events[key] === 'function') { + this.addEventListener(key, options.events[key]); + } else { + console.warn('event function must be function'); + } + }); } this.player_iframe = document.createElement("iframe"); this.loaded = false; this.addEventListener('loadedmetadata', () => { this.event_executor('ready'); this.loaded = true; }); this.addEventListener('loadedmetadata', () => { this.setVolume(this.volume); }); + this.addEventListener('ended', () => { if (this.isPlaylistVideoList) { this.nextVideo() } }) this.player_iframe.src = iframe_src; if (typeof options.width === 'number') { this.player_iframe.width = options.width; @@ -206,8 +235,8 @@ class invidious_embed { this.player_iframe.width = 640; this.player_iframe.style.maxWidth = '100%'; } - if (typeof options.width === 'number') { - this.player_iframe.width = options.width; + if (typeof options.height === 'number') { + this.player_iframe.height = options.height; } else { this.player_iframe.height = this.player_iframe.width * (9 / 16); } @@ -224,17 +253,26 @@ class invidious_embed { } event_executor(eventname) { - let event_parameter = {detail:undefined}; - if (eventname === 'statechange') { - event_parameter.detail = this.getPlayerState(); - } else if (eventname === 'error'){ - event_parameter.detail = this.error_code; + const execute_functions = this.eventobject[eventname]; + let return_data = { type: eventname, data: null, target: this }; + switch (eventname) { + case 'statechange': + return_data.data = this.getPlayerState(); + break; + case 'error': + return_data.data = this.error_code; } - this.eventElement.dispatchEvent(new Event(eventname,event_parameter)); + execute_functions.forEach(func => { + try { + func(return_data); + } catch (e) { + console.error(e); + } + }); } receiveMessage(message) { - const onControlAndHasWidgetId = message.data.from==='invidious_control' && message.data.widgetid===this.widgetid.toString(); + const onControlAndHasWidgetId = message.data.from === 'invidious_control' && message.data.widgetid === this.widgetid.toString(); if (onControlAndHasWidgetId) { switch (message.data.message_kind) { case 'info_return': @@ -415,7 +453,7 @@ class invidious_embed { } } if (mediaContetUrl.length > 0) { - const match_result = mediaContetUrl.match(/\/([A-Za-z0-9]{11})\//); + const match_result = mediaContetUrl.match(/\/([A-Za-z0-9]{11})/); if (match_result !== null && match_result.length === 2) { videoId = match_result[1]; } else { @@ -472,44 +510,47 @@ class invidious_embed { } loadVideoById(option, startSeconds) { + this.isPlaylistVideoList = false; this.playOtherVideoById(option, true, startSeconds, {}); } cueVideoById(option, startSeconds) { + this.isPlaylistVideoList = false; this.playOtherVideoById(option, false, startSeconds, {}); } cueVideoByUrl(option, startSeconds) { + this.isPlaylistVideoList = false; this.playOtherVideoById(option, false, startSeconds, {}); } loadVideoByUrl(option, startSeconds) { + this.isPlaylistVideoList = false; this.playOtherVideoById(option, true, startSeconds, {}); } - async playPlaylist(playlistData,autoplay,index,startSeconds) { + async playPlaylist(playlistData, autoplay, index, startSeconds) { let playlistId; if (typeof playlistData === 'string') { - this.playlistVideoIds = await this.getPlaylistVideoids(playlistData); - playlistId = playlistData; + this.playlistVideoIds = [playlistData]; + this.isPlaylistVideoList = true; } else if (typeof playlistData === 'object') { if (Array.isArray(playlistData)) { this.playlistVideoIds = playlistData; + this.isPlaylistVideoList = true; } else { index = playlistData['index']; let listType = 'playlist'; - if (typeof playlistData['listType'] === 'string'){ + if (typeof playlistData['listType'] === 'string') { listType = playlistData['listType']; } switch (listType) { case 'playlist': - if (Array.isArray(playlistData['list'])) { - this.playlistVideoIds = playlistData['list']; - } else if(typeof playlistData['list'] === 'string') { + if (typeof playlistData['list'] === 'string') { this.playlistVideoIds = await this.getPlaylistVideoids(playlistData['list']); playlistId = playlistData['list']; } else { - console.error('playlist data list must be string or array of strings'); + console.error('playlist data list must be string'); return; } break; @@ -521,44 +562,52 @@ class invidious_embed { return; } } + if (typeof playlistData.startSeconds === 'number') { + startSeconds = playlistData.startSeconds; + } } else { console.error('playlist function first argument must be string or array of string'); return; } - if (this.playlistVideoIds.length === 0){ + if (this.playlistVideoIds.length === 0) { console.error('playlist length 0 is invalid'); return; } - let parameter = {index:0}; + let parameter = { index: 0 }; if (typeof index === 'undefined') { index = 0; - } else if (typeof index === 'number'){ + } else if (typeof index === 'number') { parameter.index = index; - } else{ + } else { console.error('index must be number of undefined'); } - if (typeof playlistId === 'string') { + if (typeof playlistId === 'string') { parameter['list'] = playlistId; this.playlistId = playlistId; } - this.playOtherVideoById(this.playlistVideoIds[index],autoplay,startSeconds,parameter); + this.sub_index = parameter.index; + if (index >= this.playlistVideoIds.length) { + index = 0; + parameter.index = 0; + } + this.playOtherVideoById(this.playlistVideoIds[index], autoplay, startSeconds, parameter); } - cuePlaylist(data,index,startSeconds) { - this.playPlaylist(data,false,index,startSeconds); + cuePlaylist(data, index, startSeconds) { + this.playPlaylist(data, false, index, startSeconds); } - loadPlaylist(data,index,startSeconds) { - this.playPlaylist(data,true,index,startSeconds); + loadPlaylist(data, index, startSeconds) { + this.playPlaylist(data, true, index, startSeconds); } playVideoAt(index) { if (typeof index === 'number') { - let parameter = {index:index}; + let parameter = { index: index }; if (this.playlistId !== undefined) { parameter['list'] = this.playlistId; } - this.playOtherVideoById(this.playlistVideoIds[index],true,0,parameter); + this.playOtherVideoById(this.playlistVideoIds[index], true, 0, parameter); } else { console.error('playVideoAt first argument must be number'); } @@ -566,7 +615,10 @@ class invidious_embed { async nextVideo() { let now_index = this.promise_send_event('getplaylistindex'); - if (now_index === this.playlistVideoIds.length -1) { + if (now_index === null) { + now_index = this.sub_index; + } + if (now_index === this.playlistVideoIds.length - 1) { if (this.loop) { now_index = 0; } else { @@ -576,18 +628,22 @@ class invidious_embed { } else { now_index++; } - let parameter = {index:now_index}; + this.sub_index = now_index; + let parameter = { index: now_index }; if (this.playlistId !== undefined) { parameter['list'] = this.playlistId; } - this.playOtherVideoById(this.playlistVideoIds[now_index],true,0,parameter); + this.playOtherVideoById(this.playlistVideoIds[now_index], true, 0, parameter); } async previousVideo() { let now_index = this.promise_send_event('getplaylistindex'); + if (now_index === null) { + now_index = this.sub_index; + } if (now_index === 0) { if (this.loop) { - now_index = this.playlistVideoIds.length -1; + now_index = this.playlistVideoIds.length - 1; } else { console.log('back to start of playlist'); return; @@ -595,11 +651,12 @@ class invidious_embed { } else { now_index--; } - let parameter = {index:now_index}; + this.sub_index = now_index; + let parameter = { index: now_index }; if (this.playlistId !== undefined) { parameter['list'] = this.playlistId; } - this.playOtherVideoById(this.playlistVideoIds[now_index],true,0,parameter); + this.playOtherVideoById(this.playlistVideoIds[now_index], true, 0, parameter); } getDuration() { @@ -621,7 +678,7 @@ class invidious_embed { async getVideoData() { const videoData = await this.videodata_api(this.videoId); - return { video_id: this.videoId, title: await this.promise_send_event('gettitle'), list: await this.promise_send_event('getplaylistid'), isListed:videoData.isListed, isLive:videoData.liveNow, isPremiere:videoData.premium}; + return { video_id: this.videoId, title: await this.promise_send_event('gettitle'), list: await this.promise_send_event('getplaylistid'), isListed: videoData.isListed, isLive: videoData.liveNow, isPremiere: videoData.premium }; } getPlaylistIndex() { @@ -659,9 +716,9 @@ function invidious_ready(func) { invidious_embed.invidious_instance = new URL(document.currentScript.src).origin; const invidious = { Player: invidious_embed, PlayerState: { ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3, CUED: 5 }, ready: invidious_ready }; if (typeof onInvidiousIframeAPIReady === 'function') { - try{ + try { onInvidiousIframeAPIReady(); - } catch(e) { + } catch (e) { console.error(e); } } From 00ae96a7e5ea8446d5823b7b90ebd761d7f966e8 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Sat, 23 Sep 2023 18:13:54 +0900 Subject: [PATCH 23/27] Code easy to read by suggestion from code review Co-authored-by: Samantaz Fox --- assets/js/embed.js | 15 ++++++++++++++- assets/js/invidious_iframe_api.js | 25 +++++++++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 0a08e24b..20f5320b 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -83,7 +83,20 @@ function return_message(message, target_window) { } if (message.message_kind === 'event') { if (message.eventname === 'timeupdate' || message.eventname === 'loadedmetadata') { - additional_info['value'] = { getvolume: player.volume(), getduration: player.duration(), getcurrenttime: player.currentTime(), getplaystatus: player.paused(), getplaybackrate: player.playbackRate(), getloopstatus: player.loop(), getmutestatus: player.muted(), getfullscreenstatus: player.isFullscreen(), getavailableplaybackrates: options.playbackRates, gettitle: player_data.title, getplaylistindex: video_data.index, getplaylistid: video_data.plid }; + additional_info['value'] = { + getvolume: player.volume(), + getduration: player.duration(), + getcurrenttime: player.currentTime(), + getplaystatus: player.paused(), + getplaybackrate: player.playbackRate(), + getloopstatus: player.loop(), + getmutestatus: player.muted(), + getfullscreenstatus: player.isFullscreen(), + getavailableplaybackrates: options.playbackRates, + gettitle: player_data.title, + getplaylistindex: video_data.index, + getplaylistid: video_data.plid + }; } } if (message.eventname === 'error') { diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 2a6e9462..57df5ffe 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -54,11 +54,7 @@ class invidious_embed { const instance_stats = await fetch(instance_origin + '/api/v1/stats'); if (instance_stats.ok) { const instance_stats_json = await instance_stats.json(); - if (instance_stats_json.software.name === 'invidious') { - return_status = true; - } else { - return_status = false; - } + return_status = (instance_stats_json.software.name === 'invidious'); } else { return_status = false; } @@ -139,7 +135,24 @@ class invidious_embed { this.volume = 100; this.loop = false; this.playlistVideoIds = []; - this.eventobject = { ready: [], ended: [], error: [], ratechange: [], volumechange: [], waiting: [], timeupdate: [], loadedmetadata: [], play: [], seeking: [], seeked: [], playerresize: [], pause: [], statechange: [] }; + + this.eventobject = { + ready: [], + ended: [], + error: [], + ratechange: [], + volumechange: [], + waiting: [], + timeupdate: [], + loadedmetadata: [], + play: [], + seeking: [], + seeked: [], + playerresize: [], + pause: [], + statechange: [] + }; + let replace_elemnt; this.isPlaylistVideoList = false; if (element === undefined || element === null) { From afc521ef451d9070819937775e0a3964f7f4147b Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Sun, 24 Sep 2023 20:15:38 +0900 Subject: [PATCH 24/27] delete field from api and add escape embed code --- assets/js/invidious_iframe_api.js | 36 +++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 57df5ffe..52f770f3 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -103,7 +103,7 @@ class invidious_embed { async videodata_api(videoid) { const not_in_videodata_cahce = !(videoid in invidious_embed.videodata_cahce); if (not_in_videodata_cahce) { - const video_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/videos/" + videoid + "?fields=title,videoId,paid,premium,isFamilyFriendly,isListed,liveNow"); + const video_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/videos/" + videoid); if (video_api_response.ok) { invidious_embed.videodata_cahce[videoid] = Object.assign({}, { status: true }, await video_api_response.json()); } else { @@ -680,9 +680,37 @@ class invidious_embed { return this.target_origin + "/watch?v=" + this.videoId; } - async getVideoEmbedCode() { - const title = await this.getVideoTitle(); - return ''; + getTitle(){ + return this.promise_send_event('gettitle'); + } + + getVideoEmbedCode() { + //const title = invidious_embed.api_promise? this.getTitle():await this.getTitle();] + const embed_url = encodeURI(`${this.target_origin}/embed/${this.videoId}`); + const html_escape = (html)=>{ + const html_escaped = html.replace(/[&'`"<>]/g, match=>{ + return { + '&': '&', + "'": ''', + '`': '`', + '"': '"', + '<': '<', + '>': '>', + }[match]}); + return html_escaped; + } + const iframe_constractor = (raw_title)=>{ + const html_escaped_title = html_escape(raw_title); + return ``; + } + if(invidious_embed.api_promise){ + return new Promise(async(resolve,reject)=>{ + resolve(iframe_constractor(await this.getTitle())); + }) + } + else{ + return iframe_constractor(this.getTitle()); + } } getCurrentTime() { From 7af39c2d4e8a37e4a0060d352d491429d6cdca38 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Sun, 24 Sep 2023 23:04:09 +0900 Subject: [PATCH 25/27] code more easy to read --- assets/js/embed.js | 24 ++++- assets/js/invidious_iframe_api.js | 145 ++++++++++++++++++++++++------ 2 files changed, 142 insertions(+), 27 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 20f5320b..92350662 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -78,9 +78,11 @@ function return_message(message, target_window) { let url_params = new URLSearchParams(location.search); let widgetid = url_params.get('widgetid'); let additional_info = { from: 'invidious_control' }; + if (widgetid !== null) { additional_info.widgetid = widgetid; } + if (message.message_kind === 'event') { if (message.eventname === 'timeupdate' || message.eventname === 'loadedmetadata') { additional_info['value'] = { @@ -99,9 +101,11 @@ function return_message(message, target_window) { }; } } + if (message.eventname === 'error') { additional_info['value'] = { geterrorcode: player.error().code }; } + message = Object.assign(additional_info, message); let target_origin = url_params.get('origin') || '*'; target_window.postMessage(message, target_origin); @@ -193,6 +197,7 @@ function control_embed_iframe(message) { console.info("Unhandled event name: " + message.data.eventname); break; } + if (message_return_value !== undefined) { return_message({ command: message.data.eventname, value: message_return_value, message_kind: 'info_return' }, message.source); } @@ -202,8 +207,23 @@ function control_embed_iframe(message) { if (new URLSearchParams(location.search).get('enablejsapi') === '1') { window.addEventListener('message', control_embed_iframe); - const event_list = ['ended', 'error', 'ratechange', 'volumechange', 'waiting', 'timeupdate', 'loadedmetadata', 'play', 'seeking', 'seeked', 'playerresize', 'pause']; + const event_list = [ + 'ended', + 'error', + 'ratechange', + 'volumechange', + 'waiting', + 'timeupdate', + 'loadedmetadata', + 'play', + 'seeking', + 'seeked', + 'playerresize', + 'pause' + ]; event_list.forEach(event_name => { - player.on(event_name, function () { return_message({ message_kind: 'event', eventname: event_name }) }); + player.on(event_name, function () { + return_message({ message_kind: 'event', eventname: event_name }) + }); }); } diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 52f770f3..e8a067ab 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -1,7 +1,29 @@ class invidious_embed { static widgetid = 0; - static eventname_table = { onPlaybackRateChange: 'ratechange', onStateChange: 'statechange', onError: 'error', onReady: 'ready' }; - static available_event_name = ['ready', 'ended', 'error', 'ratechange', 'volumechange', 'waiting', 'timeupdate', 'loadedmetadata', 'play', 'seeking', 'seeked', 'playerresize', 'pause']; + + static eventname_table = { + onPlaybackRateChange: 'ratechange', + onStateChange: 'statechange', + onError: 'error', + onReady: 'ready' + }; + + static available_event_name = [ + 'ready', + 'ended', + 'error', + 'ratechange', + 'volumechange', + 'waiting', + 'timeupdate', + 'loadedmetadata', + 'play', + 'seeking', + 'seeked', + 'playerresize', + 'pause' + ]; + static api_promise = false; static invidious_instance = ''; static api_instance_list = []; @@ -33,12 +55,13 @@ class invidious_embed { console.warn('removeEventListner cannot find such eventname : ' + eventname); return; } - this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(x => { - const allowFunctionDetected = x.toString()[0] === '('; + + this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(listed_function => { + const allowFunctionDetected = listed_function.toString()[0] === '('; if (allowFunctionDetected) { - x.toString() !== func.toString(); + listed_function.toString() !== func.toString(); } else { - x !== func; + listed_function !== func; } }); } else { @@ -162,19 +185,23 @@ class invidious_embed { } else { replace_elemnt = element; } + let iframe_src = ''; if (options.host !== undefined && options.host !== "") { iframe_src = new URL(options.host).origin; } else if (invidious_embed.invidious_instance !== '') { iframe_src = invidious_embed.invidious_instance; } + if (!await this.instance_access_check(iframe_src)) { await this.auto_instance_select(); iframe_src = invidious_embed.invidious_instance; } + invidious_embed.invidious_instance = iframe_src; this.target_origin = iframe_src; iframe_src += '/embed/'; + if (typeof options.videoId === 'string' && options.videoId.length === 11) { iframe_src += options.videoId; this.videoId = options.videoId; @@ -188,12 +215,14 @@ class invidious_embed { this.event_executor('error'); return; } + let search_params = new URLSearchParams(''); search_params.append('widgetid', invidious_embed.widgetid); this.widgetid = invidious_embed.widgetid; invidious_embed.widgetid++; search_params.append('origin', location.origin); search_params.append('enablejsapi', '1'); + let no_start_parameter = true; if (typeof options.playerVars === 'object') { this.option_playerVars = options.playerVars; @@ -214,19 +243,25 @@ class invidious_embed { console.warn('player vars key must be string'); } }); + if (options.playerVars.start !== undefined) { no_start_parameter = false; } + if (options.playerVars.autoplay === undefined) { search_params.append('autoplay', '0'); } + } else { search_params.append('autoplay', '0'); } + if (no_start_parameter) { search_params.append('start', '0'); } + iframe_src += "?" + search_params.toString(); + if (typeof options.events === 'object') { Object.keys(options.events).forEach(key => { if (typeof options.events[key] === 'function') { @@ -236,23 +271,27 @@ class invidious_embed { } }); } + this.player_iframe = document.createElement("iframe"); this.loaded = false; this.addEventListener('loadedmetadata', () => { this.event_executor('ready'); this.loaded = true; }); this.addEventListener('loadedmetadata', () => { this.setVolume(this.volume); }); this.addEventListener('ended', () => { if (this.isPlaylistVideoList) { this.nextVideo() } }) this.player_iframe.src = iframe_src; + if (typeof options.width === 'number') { this.player_iframe.width = options.width; } else { this.player_iframe.width = 640; this.player_iframe.style.maxWidth = '100%'; } + if (typeof options.height === 'number') { this.player_iframe.height = options.height; } else { this.player_iframe.height = this.player_iframe.width * (9 / 16); } + this.player_iframe.style.border = "none"; replace_elemnt.replaceWith(this.player_iframe); this.eventdata = {}; @@ -260,14 +299,22 @@ class invidious_embed { } postMessage(data) { - const additionalInfo = { 'origin': location.origin, 'widgetid': this.widgetid.toString(), 'target': 'invidious_control' }; + const additionalInfo = { + 'origin': location.origin, + 'widgetid': this.widgetid.toString(), + 'target': 'invidious_control' + }; data = Object.assign(additionalInfo, data); this.player_iframe.contentWindow.postMessage(data, this.target_origin); } event_executor(eventname) { const execute_functions = this.eventobject[eventname]; - let return_data = { type: eventname, data: null, target: this }; + let return_data = { + type: eventname, + data: null, + target: this + }; switch (eventname) { case 'statechange': return_data.data = this.getPlayerState(); @@ -334,8 +381,10 @@ class invidious_embed { promise_send_event(event_name) { if (invidious_embed.api_promise) { - const promise_object = new Promise((resolve, reject) => { this.message_wait[event_name].push(resolve) }); - this.postMessage({ eventname: event_name }); + const promise_object = new Promise((resolve, reject) => this.message_wait[event_name].push(resolve) ); + this.postMessage({ + eventname: event_name + }); return promise_object; } else { return this.eventdata[event_name]; @@ -389,7 +438,7 @@ class invidious_embed { return this.promise_send_event('getmutestatus'); } - async seekTo(seconds, allowSeekAhead) {//seconds must be a number and allowSeekAhead is ignore + seekTo(seconds, allowSeekAhead) {//seconds must be a number and allowSeekAhead is ignore if (typeof seconds === 'number') { if (seconds !== NaN && seconds !== undefined) { this.postMessage({ eventname: 'seek', value: seconds }); @@ -433,17 +482,20 @@ class invidious_embed { let startSeconds = 0; let endSeconds = -1; let mediaContetUrl = ''; + if (typeof option === 'string') { if (option.length === 11) { - videoId = option + videoId = option; } else { mediaContetUrl = option; } + if (typeof startSeconds_arg === 'number') { startSeconds = startSeconds_arg; } } else if (typeof option === 'object') { if (typeof option.videoId === 'string') { + if (option.videoId.length == 11) { videoId = option.videoId; } else { @@ -458,9 +510,11 @@ class invidious_embed { this.event_executor('error'); return; } + if (typeof option.startSeconds === 'number' && option.startSeconds >= 0) { startSeconds = option.startSeconds; } + if (typeof option.endSeconds === 'number' && option.endSeconds >= 0) { endSeconds = option.endSeconds; } @@ -475,14 +529,17 @@ class invidious_embed { return; } } + let iframe_sorce = this.target_origin.slice(); iframe_sorce += "/embed/" + videoId; this.videoId = videoId; + if (!await this.videoid_accessable_check(videoId)) { this.error_code = 100; this.event_executor('error'); return; } + let search_params = new URLSearchParams(''); search_params.append('origin', location.origin); search_params.append('enablejsapi', '1'); @@ -490,6 +547,7 @@ class invidious_embed { this.widgetid = invidious_embed.widgetid; invidious_embed.widgetid++; search_params.append('autoplay', Number(autoplay)); + if (this.option_playerVars !== undefined) { const ignore_keys = ['autoplay', 'start', 'end', 'index', 'list']; Object.keys(this.option_playerVars).forEach(key => { @@ -498,6 +556,7 @@ class invidious_embed { } }) } + if (typeof additional_argument === 'object') { const ignore_keys = ['autoplay', 'start', 'end']; Object.keys(additional_argument).forEach(key => { @@ -506,6 +565,7 @@ class invidious_embed { } }) } + search_params.append('start', startSeconds); if (endSeconds !== -1 && endSeconds >= 0) { if (endSeconds > startSeconds) { @@ -514,8 +574,10 @@ class invidious_embed { throw 'Invalid end seconds because end seconds before start seconds'; } } + iframe_sorce += "?" + search_params.toString(); this.player_iframe.src = iframe_sorce; + if (autoplay) { this.player_status = 5; } @@ -557,6 +619,7 @@ class invidious_embed { if (typeof playlistData['listType'] === 'string') { listType = playlistData['listType']; } + switch (listType) { case 'playlist': if (typeof playlistData['list'] === 'string') { @@ -575,6 +638,7 @@ class invidious_embed { return; } } + if (typeof playlistData.startSeconds === 'number') { startSeconds = playlistData.startSeconds; } @@ -582,6 +646,7 @@ class invidious_embed { console.error('playlist function first argument must be string or array of string'); return; } + if (this.playlistVideoIds.length === 0) { console.error('playlist length 0 is invalid'); return; @@ -594,11 +659,13 @@ class invidious_embed { } else { console.error('index must be number of undefined'); } + if (typeof playlistId === 'string') { parameter['list'] = playlistId; this.playlistId = playlistId; } this.sub_index = parameter.index; + if (index >= this.playlistVideoIds.length) { index = 0; parameter.index = 0; @@ -631,6 +698,7 @@ class invidious_embed { if (now_index === null) { now_index = this.sub_index; } + if (now_index === this.playlistVideoIds.length - 1) { if (this.loop) { now_index = 0; @@ -680,15 +748,14 @@ class invidious_embed { return this.target_origin + "/watch?v=" + this.videoId; } - getTitle(){ + getTitle() { return this.promise_send_event('gettitle'); } getVideoEmbedCode() { - //const title = invidious_embed.api_promise? this.getTitle():await this.getTitle();] const embed_url = encodeURI(`${this.target_origin}/embed/${this.videoId}`); - const html_escape = (html)=>{ - const html_escaped = html.replace(/[&'`"<>]/g, match=>{ + const html_escape = (html) => { + const html_escaped = html.replace(/[&'`"<>]/g, match => { return { '&': '&', "'": ''', @@ -696,19 +763,20 @@ class invidious_embed { '"': '"', '<': '<', '>': '>', - }[match]}); + }[match] + }); return html_escaped; } - const iframe_constractor = (raw_title)=>{ + const iframe_constractor = (raw_title) => { const html_escaped_title = html_escape(raw_title); - return ``; + return ``; } - if(invidious_embed.api_promise){ - return new Promise(async(resolve,reject)=>{ + if (invidious_embed.api_promise) { + return new Promise(async (resolve, reject) => { resolve(iframe_constractor(await this.getTitle())); }) } - else{ + else { return iframe_constractor(this.getTitle()); } } @@ -719,7 +787,14 @@ class invidious_embed { async getVideoData() { const videoData = await this.videodata_api(this.videoId); - return { video_id: this.videoId, title: await this.promise_send_event('gettitle'), list: await this.promise_send_event('getplaylistid'), isListed: videoData.isListed, isLive: videoData.liveNow, isPremiere: videoData.premium }; + return { + video_id: this.videoId, + title: await this.promise_send_event('gettitle'), + list: await this.promise_send_event('getplaylistid'), + isListed: videoData.isListed, + isLive: videoData.liveNow, + isPremiere: videoData.premium + }; } getPlaylistIndex() { @@ -741,7 +816,15 @@ class invidious_embed { constructor(element, options) { this.Player(element, options); window.addEventListener('message', (ms) => { this.receiveMessage(ms) }); - this.message_wait = { getvolume: [], getmutestatus: [], getduration: [], getcurrenttime: [], getplaybackrate: [], getavailableplaybackrates: [], gettitle: [] }; + this.message_wait = { + getvolume: [], + getmutestatus: [], + getduration: [], + getcurrenttime: [], + getplaybackrate: [], + getavailableplaybackrates: [], + gettitle: [] + }; } } @@ -755,7 +838,19 @@ function invidious_ready(func) { } invidious_embed.invidious_instance = new URL(document.currentScript.src).origin; -const invidious = { Player: invidious_embed, PlayerState: { ENDED: 0, PLAYING: 1, PAUSED: 2, BUFFERING: 3, CUED: 5 }, ready: invidious_ready }; + +const invidious = { + Player: invidious_embed, + PlayerState: { + ENDED: 0, + PLAYING: 1, + PAUSED: 2, + BUFFERING: 3, + CUED: 5 + }, + ready: invidious_ready +}; + if (typeof onInvidiousIframeAPIReady === 'function') { try { onInvidiousIframeAPIReady(); From bd4a4ab17b5b26b4a457bab38426b34c4c15bebf Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Tue, 26 Sep 2023 00:06:47 +0900 Subject: [PATCH 26/27] add jsdoc for iframe api --- assets/js/invidious_iframe_api.js | 585 +++++++++++++++++++++++++++++- 1 file changed, 573 insertions(+), 12 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index e8a067ab..55a09cc1 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -24,18 +24,32 @@ class invidious_embed { 'pause' ]; + /** + * Recive event response synchronization or asynchronous. + * + * Default false mean synchronization + * @type {boolean} + */ static api_promise = false; static invidious_instance = ''; + /** + * @type {[string]} + */ static api_instance_list = []; static instance_status_list = {}; static videodata_cahce = {}; - addEventListener(eventname, func) { - if (typeof func === 'function') { + /** + * Add event execute function for player + * @param {string} eventname + * @param {Function} event_execute_function + */ + addEventListener(eventname, event_execute_function) { + if (typeof event_execute_function === 'function') { if (eventname in invidious_embed.eventname_table) { - this.eventobject[invidious_embed.eventname_table[eventname]].push(func); + this.eventobject[invidious_embed.eventname_table[eventname]].push(event_execute_function); } else if (invidious_embed.available_event_name.includes(eventname)) { - this.eventobject[eventname].push(func); + this.eventobject[eventname].push(event_execute_function); } else { console.warn('addEventListener cannot find such eventname : ' + eventname); } @@ -44,8 +58,13 @@ class invidious_embed { } } - removeEventListener(eventname, func) { - if (typeof func === 'function') { + /** + * remove spacific event execute function + * @param {string} eventname + * @param {Function} delete_event_function + */ + removeEventListener(eventname, delete_event_function) { + if (typeof delete_event_function === 'function') { let internal_eventname; if (eventname in invidious_embed.eventname_table) { internal_eventname = invidious_embed.eventname_table[eventname]; @@ -59,9 +78,9 @@ class invidious_embed { this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(listed_function => { const allowFunctionDetected = listed_function.toString()[0] === '('; if (allowFunctionDetected) { - listed_function.toString() !== func.toString(); + listed_function.toString() !== delete_event_function.toString(); } else { - listed_function !== func; + listed_function !== delete_event_function; } }); } else { @@ -69,6 +88,11 @@ class invidious_embed { } } + /** + * return whether instance_origin origin can use or not + * @param {string} instance_origin + * @returns {Promise} + */ async instance_access_check(instance_origin) { let return_status; const status_cahce_exist = instance_origin in invidious_embed.instance_status_list; @@ -91,6 +115,13 @@ class invidious_embed { } } + /** + * Need to use await + * + * Add invidious_embed.api_instance_list + * + * fetch from api.invidious.io + */ async get_instance_list() { invidious_embed.api_instance_list = []; const instance_list_api = await (await fetch('https://api.invidious.io/instances.json?pretty=1&sort_by=type,users')).json(); @@ -107,6 +138,11 @@ class invidious_embed { }); } + /** + * Need to use await + * + * Auto select invidious instance and set invidious_embed.invidious_instance + */ async auto_instance_select() { if (await this.instance_access_check(invidious_embed.invidious_instance)) { return; @@ -123,6 +159,112 @@ class invidious_embed { } } + /** + * Need to use await + * Return videoData using invidious videos api + * @param {string} videoid + * @returns {Promise<{ + * title:string, + * videoId:string, + * videoThumbnails:[{ + * quarity:string, + * url:string, + * height:number, + * width:number + * }], + * storyboards:[{ + * url:string, + * templateUrl:string, + * width:number, + * height:number, + * count:number, + * interval:number, + * storyboardWidth:number, + * storyboardHeight:number, + * storyboardCount:number + * }] + * description:string, + * descriptionHtml:string, + * published:number, + * publishedText:string, + * keywords:[string], + * viewCount:number, + * likeCount:number, + * dislikeCount:number, + * paid:boolean, + * premium:boolean, + * isFamilyFriendly:boolean, + * allowedRegions:[string], + * genre:string, + * genreUrl:string, + * author:string, + * authorId:string, + * authorUrl:string, + * authorThumbnails:[{ + * url:string, + * width:number, + * height:number + * }] + * subCountText:string, + * lengthSeconds:number, + * allowRatings:string, + * rating:number, + * isListed:boolean, + * liveNow:boolean, + * isUpcoming:boolean, + * dashUrl:string, + * adaptiveFormats:[{ + * init:string, + * index:string, + * bitrate:string, + * url:string, + * itag:string, + * type:string, + * clen:string, + * lmt:string, + * projectionType:string, + * fps:number, + * container:string, + * encoding:string, + * audioQuality:string, + * audioSampleRate:number, + * audioChannels:number + * }] + * formatStreams:[{ + * url:string, + * itag:string, + * type:string, + * quarity:string, + * fps:number, + * container:string, + * encoding:string, + * resolution:string, + * qualityLabel:string, + * size:string + * }] + * captions:[{ + * label:string, + * language_code:string, + * url:string + * }] + * recommendedVideos:[{ + * videoId:string, + * title:string, + * videoThumbnails:[{ + * quarity:string, + * url:string, + * height:number, + * width:number + * }], + * author:string, + * authorId:string, + * authorUrl:string, + * lengthSeconds:number, + * viewCountText:string, + * viewCount:number + * }] + * }>} + */ async videodata_api(videoid) { const not_in_videodata_cahce = !(videoid in invidious_embed.videodata_cahce); if (not_in_videodata_cahce) { @@ -136,10 +278,22 @@ class invidious_embed { return invidious_embed.videodata_cahce[videoid]; } + /** + * Need to use await + * check whether videoid exist or not + * @param {string} videoid + * @returns {promise} + */ async videoid_accessable_check(videoid) { return (await this.videodata_api(videoid)).status; } + /** + * Need to use await + * return array of videoid in playlistid + * @param {string} playlistid + * @returns {Promise<[string]>} + */ async getPlaylistVideoids(playlistid) { const playlist_api_response = await fetch(invidious_embed.invidious_instance + "/api/v1/playlists/" + playlistid); if (playlist_api_response.ok) { @@ -152,6 +306,26 @@ class invidious_embed { } } + /** + * + * @param {string|Node} element + * @param {{ + * videoId:string, + * host:string, + * playerVars:{ + * start:number|string, + * end:number|string, + * autoplay:number|string + * }, + * events:{ + * onReady:Function, + * onError:Function, + * onStateChange:Function, + * onPlaybackRateChange:Function + * } + * }} options + * @returns + */ async Player(element, options) { this.player_status = -1; this.error_code = 0; @@ -182,6 +356,10 @@ class invidious_embed { throw 'Please, pass element id or HTMLElement as first argument'; } else if (typeof element === 'string') { replace_elemnt = document.getElementById(element); + + if(replace_elemnt === null){ + throw 'Can not find spacific element' + } } else { replace_elemnt = element; } @@ -294,6 +472,9 @@ class invidious_embed { this.player_iframe.style.border = "none"; replace_elemnt.replaceWith(this.player_iframe); + /** + * @type {Object.} + */ this.eventdata = {}; return this; } @@ -308,6 +489,10 @@ class invidious_embed { this.player_iframe.contentWindow.postMessage(data, this.target_origin); } + /** + * execute eventname event + * @param {string} eventname + */ event_executor(eventname) { const execute_functions = this.eventobject[eventname]; let return_data = { @@ -331,6 +516,19 @@ class invidious_embed { }); } + /** + * + * @param {{ + * data:{ + * from:string, + * message_kind:string, + * widgetid:string, + * command:string, + * value:string|number|object|null, + * eventname:string + * } + * }} message + */ receiveMessage(message) { const onControlAndHasWidgetId = message.data.from === 'invidious_control' && message.data.widgetid === this.widgetid.toString(); if (onControlAndHasWidgetId) { @@ -379,6 +577,15 @@ class invidious_embed { } } + /** + * Default return no Promise value. + * + * But if set invidious_embed.api_promise true, return Promise value + * + * send eventname event to player iframe + * @param {'getvolume'|'setvolume'|'getmutestatus'|'getplaybackrate'|'getavailableplaybackrates'|'getplaylistindex'|'getduration'|'gettitle'|'getplaylistid'|'getcurrenttime'} event_name + * @returns {number|boolean|[number]|string|Promise|Promise|Promise<[number]>|Promise} + */ promise_send_event(event_name) { if (invidious_embed.api_promise) { const promise_object = new Promise((resolve, reject) => this.message_wait[event_name].push(resolve) ); @@ -391,22 +598,69 @@ class invidious_embed { } } + /** + * return playerstatus same as youtube iframe api + * + * -1:unstarted + * + * 0:ended + * + * 1:playing + * + * 2:paused + * + * 3:buffering + * + * 5:video cued + * @returns {number} + * @example + * const player_statrus = player.getPlayerState(); + * //player_statrus = 1; + */ getPlayerState() { return this.player_status; } + /** + * send play command to iframe player + * @example + * player.playVideo(); + */ playVideo() { this.postMessage({ eventname: 'play' }); } + /** + * send pause command to iframe player + * @example + * player.pauseVideo(); + */ pauseVideo() { this.postMessage({ eventname: 'pause' }); } + /** + * Default return number range 0 to 100 + * + * But if set invidious_embed.api_promise true, return Promise + * @returns {number|Promise} + * @example + * const volume = player.getVolume();//invidious_embed.api_promise is false + * const volume = await player.getVolume();//invidious_embed.api_promise is true + * //volume = 100 + */ getVolume() { return this.promise_send_event('getvolume'); } + /** + * Send set volume event to iframe player + * + * volume must be range 0 to 100 + * @param {number} volume + * @example + * player.setVolume(50);//set volume 50% + */ setVolume(volume) { if (typeof volume === 'number') { this.volume = volume; @@ -418,27 +672,69 @@ class invidious_embed { } } + /** + * Get player iframe node + * @returns {Node} + * @example + * const invidious_player_node = player.getIframe(); + */ getIframe() { return this.player_iframe; } + /** + * delete player iframe + * @example + * player.destroy(); + */ destroy() { this.player_iframe.remove(); } + /** + * send mute event to iframe player + * @example + * player.mute(); + */ mute() { this.postMessage({ eventname: 'setmutestatus', value: true }); } + /** + * send unmute event to iframe player + * @example + * player.unMute(); + */ unMute() { this.postMessage({ eventname: 'setmutestatus', value: false }); } + /** + * Whether mute or not. + * + * Default return boolean. + * + * But if set invidious_embed.api_promise true, return Promise. + * @returns {boolean|Promise} + * @example + * const muteStatus = player.isMuted();//invidious_embed.api_promise false + * const muteStatus = await player.isMuted();//invidious_embed.api_promise true + * //muteStatus = false + */ isMuted() { return this.promise_send_event('getmutestatus'); } - seekTo(seconds, allowSeekAhead) {//seconds must be a number and allowSeekAhead is ignore + /** + * send command seek video to seconds to iframe player. + * + * seconds count start with video 0 seconds. + * @param {number} seconds + * @param {boolean} allowSeekAhead ignore. only maintained for compatibility of youtube iframe player + * @example + * player.seekTo(100);//seek to 100 seconds of video which counts start with 0 seconds of the video + */ + seekTo(seconds, allowSeekAhead) { if (typeof seconds === 'number') { if (seconds !== NaN && seconds !== undefined) { this.postMessage({ eventname: 'seek', value: seconds }); @@ -448,7 +744,14 @@ class invidious_embed { } } - setSize(width, height) {//width and height must be Number + /** + * set iframe size + * @param {number} width + * @param {number} height + * @example + * player.setSize(480,270); + */ + setSize(width, height) { if (typeof width === 'number' && typeof height === 'number') { this.player_iframe.width = width; this.player_iframe.height = height; @@ -457,11 +760,30 @@ class invidious_embed { } } + /** + * get playback rate. + * + * Default return number. + * + * But if set invidious_embed.api_promise true, return Promise. + * @returns {number|Promise} + * @example + * const now_playback_rate = player.getPlaybackRate();//invidious_embed.api_promise is false + * const now_playback_rate = await player.getPlaybackRate();//invidious_embed.api_promise is true + * //now_playback_rate = 1.0 + */ getPlaybackRate() { return this.promise_send_event('getplaybackrate'); } - setPlaybackRate(suggestedRate) {//suggestedRate must be number.this player allow not available playback rate such as 1.4 + /** + * Set video play back rate + * @param {number} suggestedRate + * @example + * player.setPlaybackRate(0.5);//play video 0.5x + * player.setPlaybackRate(1.2);//play video 1.2x + */ + setPlaybackRate(suggestedRate) { if (typeof suggestedRate === 'number') { if (suggestedRate !== NaN) { this.postMessage({ eventname: 'setplaybackrate', value: suggestedRate }); @@ -473,10 +795,35 @@ class invidious_embed { } } + /** + * get available playback rates. + * + * Default return [number]. + * + * But if set invidious_embed.api_promise true, return Promise<[number]> + * @returns {[number]|Promise<[number]>} + * @example + * const available_playback_rates = player.getAvailablePlaybackRates();//invidious_embed.api_promise is false + * const available_playback_rates = player.getAvailablePlaybackRates();//invidious_embed.api_promise is true + * //available_playback_rates = [0.25,0.5,0.75,1,1.25,1.5,1.75,2.0]; + */ getAvailablePlaybackRates() { return this.promise_send_event('getavailableplaybackrates'); } + /** + * + * @param {string|{ + * videoId:string|undefined, + * mediaContentUrl:string|undefined, + * startSeconds:number, + * endSeconds:number + * }} option + * @param {boolean} autoplay + * @param {number|undefined} startSeconds_arg + * @param {Object.} additional_argument + * @returns + */ async playOtherVideoById(option, autoplay, startSeconds_arg, additional_argument) {//internal fuction let videoId = ''; let startSeconds = 0; @@ -584,27 +931,97 @@ class invidious_embed { this.eventdata = {}; } + /** + * Load video using videoId + * @param {string|{ + * videoId:string, + * startSeconds:number|undefined, + * endSeconds:number|undefined + * }} option + * @param {number|undefined} startSeconds + * @example + * player.loadVideoById('INHasAVlzI8');//load video INHasAVlzI8 + * player.loadVideoById('INHasAVlzI8',52);//load video INHasAVlzI8 and start with 52 seconds + * player.loadVideoById({videoId:'INHasAVlzI8',startSeconds:52,endSeconds:76});//load video INHasAVlzI8 ,start with 52 seconds and end 76 seconds + */ loadVideoById(option, startSeconds) { this.isPlaylistVideoList = false; this.playOtherVideoById(option, true, startSeconds, {}); } + /** + * Cue video using videoId + * + * Cue mean before playing video only show video thumbnail and title + * @param {string|{ + * videoId:string, + * startSeconds:number|undefined, + * endSeconds:number|undefined + * }} option + * @param {number|undefined} startSeconds + * @example + * player.cueVideoById('INHasAVlzI8');//load video INHasAVlzI8 + * player.cueVideoById('INHasAVlzI8',52);//load video INHasAVlzI8 and start with 52 seconds + * player.cueVideoById({videoId:'INHasAVlzI8',startSeconds:52,endSeconds:76});//load video INHasAVlzI8 ,start with 52 seconds and end 76 seconds + */ cueVideoById(option, startSeconds) { this.isPlaylistVideoList = false; this.playOtherVideoById(option, false, startSeconds, {}); } + /** + * Cue video using media content url + * + * Cue mean before playing video only show video thumbnail and title + * + * Media content url like https://youtube.com/v/INHasAVlzI8 .Cannot run like https://youtube.com/watch/?v=INHasAVlzI8 this behavior is same as youtube iframe api + * @param {string|{ + * mediaContentUrl:string, + * startSeconds:number|undefined, + * endSeconds:number|undefined + * }} option + * @param {number|undefined} startSeconds + * @example + * player.cueVideoByUrl('https://youtube.com/v/INHasAVlzI8');//load video INHasAVlzI8 + * player.cueVideoByUrl('https://youtube.com/v/INHasAVlzI8',52);//load video INHasAVlzI8 and start with 52 seconds + * player.cueVideoByUrl({mediaContentUrl:'https://youtube.com/v/INHasAVlzI8',startSeconds:52,endSeconds:76});//load video INHasAVlzI8 ,start with 52 seconds and end 76 seconds + */ cueVideoByUrl(option, startSeconds) { this.isPlaylistVideoList = false; this.playOtherVideoById(option, false, startSeconds, {}); } + /** + * Load video using media content url + * + * Media content url like https://youtube.com/v/INHasAVlzI8 .Cannot run like https://youtube.com/watch/?v=INHasAVlzI8 this behavior is same as youtube iframe api + * @param {string|{ + * mediaContentUrl:string, + * startSeconds:number|undefined, + * endSeconds:number|undefined + * }} option + * @param {number|undefined} startSeconds + * @example + * player.loadVideoByUrl('https://youtube.com/v/INHasAVlzI8');//load video INHasAVlzI8 + * player.loadVideoByUrl('https://youtube.com/v/INHasAVlzI8',52);//load video INHasAVlzI8 and start with 52 seconds + * player.loadVideoByUrl({mediaContentUrl:'https://youtube.com/v/INHasAVlzI8',startSeconds:52,endSeconds:76});//load video INHasAVlzI8 ,start with 52 seconds and end 76 seconds + */ loadVideoByUrl(option, startSeconds) { this.isPlaylistVideoList = false; this.playOtherVideoById(option, true, startSeconds, {}); } + /** + * + * @param {string|[string]|{index:number|undefined,list:string,listType:string|undefined}} playlistData + * @param {boolean} autoplay + * @param {number} index + * @param {number} startSeconds + */ async playPlaylist(playlistData, autoplay, index, startSeconds) { + /** + * @type {string} + */ let playlistId; if (typeof playlistData === 'string') { this.playlistVideoIds = [playlistData]; @@ -673,14 +1090,42 @@ class invidious_embed { this.playOtherVideoById(this.playlistVideoIds[index], autoplay, startSeconds, parameter); } + /** + * Cue playlist and play video at index number + * @param {string|[string]|{index:number|undefined,list:string,listType:string|undefined}} data + * @param {number|undefined} index count start with 0 + * @param {number|undefined} startSeconds only affect first video + * @example + * player.loadPlaylist('i50sUufNbzY');//play i50sUufNbzY video start with 0 second + * player.loadPlaylist(['i50sUufNbzY','BgNVwiX7K8E','L7PCS7afS3Y'],1,10);//play index second playlist (BgNVwiX7K8E) and play start with 10 seconds + * player.loadPlaylist({list:'PL84LbRiy3noqhbyqr-IcCKhyXE6mFoQzF'});//play playlist first index of PL84LbRiy3noqhbyqr-IcCKhyXE6mFoQzF and start with 0 second + */ cuePlaylist(data, index, startSeconds) { this.playPlaylist(data, false, index, startSeconds); } + /** + * Load playlist and play video at index number + * + * Cue mean before playing video only show video thumbnail and title + * @param {string|[string]|{index:number|undefined,list:string,listType:string|undefined}} data + * @param {number|undefined} index count start with 0 + * @param {number|undefined} startSeconds only affect first video + * @example + * player.loadPlaylist('i50sUufNbzY');//play i50sUufNbzY video start with 0 second + * player.loadPlaylist(['i50sUufNbzY','BgNVwiX7K8E','L7PCS7afS3Y'],1,10);//play index second playlist (BgNVwiX7K8E) and play start with 10 seconds + * player.loadPlaylist({list:'PL84LbRiy3noqhbyqr-IcCKhyXE6mFoQzF'});//play playlist first index of PL84LbRiy3noqhbyqr-IcCKhyXE6mFoQzF and start with 0 second + */ loadPlaylist(data, index, startSeconds) { this.playPlaylist(data, true, index, startSeconds); } + /** + * Play video spacific number of index + * @param {number} index count start with 0 + * @example + * player.playVideoAt(5);//play video playlist index 6th + */ playVideoAt(index) { if (typeof index === 'number') { let parameter = { index: index }; @@ -693,6 +1138,13 @@ class invidious_embed { } } + /** + * play next video of playlist + * + * if end of playlist,if loop is true, load first video of playlist. + * @example + * player.nextVideo(); + */ async nextVideo() { let now_index = this.promise_send_event('getplaylistindex'); if (now_index === null) { @@ -717,6 +1169,13 @@ class invidious_embed { this.playOtherVideoById(this.playlistVideoIds[now_index], true, 0, parameter); } + /** + * play previous video of playlist + * + * if start of playlist,if loop is true, load end video of playlist. + * @example + * player.previousVideo(); + */ async previousVideo() { let now_index = this.promise_send_event('getplaylistindex'); if (now_index === null) { @@ -740,18 +1199,61 @@ class invidious_embed { this.playOtherVideoById(this.playlistVideoIds[now_index], true, 0, parameter); } + /** + * Get dulation of video + * + * Default return number + * + * But if set invidious_embed.api_promise true, return Promise. + * @returns {number|Promise} + * @example + * const player_dulation = player.getDuration();//invidious_embed.api_promise is false + * const player_dulation = await player.getDuration();//invidious_embed.api_promise is true + * //player_dulation = 80 + */ getDuration() { return this.promise_send_event('getduration'); } + /** + * Get url of loaded video + * @returns {string} + * @example + * const video_url = player.getVideoUrl(); + * //video_url = "https://yewtu.be/watch?v=KqE7Bwhd-rE" + */ getVideoUrl() { return this.target_origin + "/watch?v=" + this.videoId; } + /** + * Get title of loaded video. + * + * Default return string + * + * But if set invidious_embed.api_promise true, return Promise. + * @returns {string,Promise} + * @example + * const title = player.getTitle();//invidious_embed.api_promise is false + * const title = await player.getTitle();//invidious_embed.api_promise is true + * //title = "【夏の終わりに】夏祭り/ときのそら【歌ってみた】" + */ getTitle() { return this.promise_send_event('gettitle'); } + /** + * Get video embed iframe string + * + * Default return string + * + * But if set invidious_embed.api_promise true, return Promise. + * @returns {string,Promise} + * @example + * const embed_code = player.getVideoEmbedCode();//invidious_embed.api_promise is false + * const embed_code = await player.getVideoEmbedCode();//invidious_embed.api_promise is true + * //embed_code = `` + */ getVideoEmbedCode() { const embed_url = encodeURI(`${this.target_origin}/embed/${this.videoId}`); const html_escape = (html) => { @@ -781,10 +1283,38 @@ class invidious_embed { } } + /** + * Get current playing time start with video 0 seconds + * + * Default return number + * + * But if set invidious_embed.api_promise true, return Promise. + * @returns {number|Promise} + * @example + * const player_time = player.getCurrentTime();//invidious_embed.api_promise is false + * const player_time = await player.getCurrentTime();//invidious_embed.api_promise is true + * //player_time = 80 + */ getCurrentTime() { return this.promise_send_event('getcurrenttime'); } + /** + * Get video related data. + * + * This function is not compatible with youtube iframe api + * @returns {Promise<{ + * video_id:string, + * title:string, + * list:?string, + * isListed:boolean, + * isLibe:boolean, + * isPremiere:boolean + * }>} + * @example + * const video_data = await player.getVideoData(); + * //video_data = {"video_id": "KqE7Bwhd-rE","title": "【夏の終わりに】夏祭り/ときのそら【歌ってみた】","list": null,"isListed": true,"isLive": false,"isPremiere": false} + */ async getVideoData() { const videoData = await this.videodata_api(this.videoId); return { @@ -797,14 +1327,39 @@ class invidious_embed { }; } + /** + * Get playlist index which count start with 0 + * + * Default return number + * + * But if set invidious_embed.api_promise true, return Promise. + * @returns {number|Promise} + * @example + * const playlist_index = player.getPlaylistIndex();//invidious_embed.api_promise is false + * const playlist_index = await player.getPlaylistIndex();//invidious_embed.api_promise is true + * //playlist_index = 3 + */ getPlaylistIndex() { return this.promise_send_event('getplaylistindex'); } + /** + * Get playlist videoIds + * @returns {[string]|undefined} + * @example + * const playlist_videoids = player.getPlaylist(); + * //playlist_videoids = ['i50sUufNbzY','BgNVwiX7K8E','L7PCS7afS3Y']; + */ getPlaylist() { return this.playlistVideoIds !== undefined ? this.playlistVideoIds : []; } + /** + * set loop video or not + * @param {boolean} loopStatus + * @example + * player.setLoop(true); + */ setLoop(loopStatus) { if (typeof loopStatus === 'boolean') { this.loop = loopStatus; @@ -828,6 +1383,12 @@ class invidious_embed { } } +/** + * After load iFrame api,function will execute + * + * But this function always execute imidiretry because iframe api ready mean load complete this js file + * @param {Function} func + */ function invidious_ready(func) { if (typeof func === 'function') { func(); @@ -837,7 +1398,7 @@ function invidious_ready(func) { } } -invidious_embed.invidious_instance = new URL(document.currentScript.src).origin; +invidious_embed.invidious_instance = new URL(document.currentScript.src).origin;//set default instance using load origin of js file instance const invidious = { Player: invidious_embed, From 0d586dde2ac609cef9c0500fe404101df1f6c617 Mon Sep 17 00:00:00 2001 From: bonjinnorenka <32708102+bonjinnorenka@users.noreply.github.com> Date: Fri, 29 Sep 2023 22:35:40 +0900 Subject: [PATCH 27/27] reduce indentation and improve jsdoc descriptions --- assets/js/invidious_iframe_api.js | 338 +++++++++++++++++------------- 1 file changed, 188 insertions(+), 150 deletions(-) diff --git a/assets/js/invidious_iframe_api.js b/assets/js/invidious_iframe_api.js index 55a09cc1..d9f64555 100644 --- a/assets/js/invidious_iframe_api.js +++ b/assets/js/invidious_iframe_api.js @@ -32,138 +32,19 @@ class invidious_embed { */ static api_promise = false; static invidious_instance = ''; + /** * @type {[string]} */ static api_instance_list = []; + + /** + * @type {Object} + */ static instance_status_list = {}; - static videodata_cahce = {}; /** - * Add event execute function for player - * @param {string} eventname - * @param {Function} event_execute_function - */ - addEventListener(eventname, event_execute_function) { - if (typeof event_execute_function === 'function') { - if (eventname in invidious_embed.eventname_table) { - this.eventobject[invidious_embed.eventname_table[eventname]].push(event_execute_function); - } else if (invidious_embed.available_event_name.includes(eventname)) { - this.eventobject[eventname].push(event_execute_function); - } else { - console.warn('addEventListener cannot find such eventname : ' + eventname); - } - } else { - console.warn("addEventListner secound args must be function"); - } - } - - /** - * remove spacific event execute function - * @param {string} eventname - * @param {Function} delete_event_function - */ - removeEventListener(eventname, delete_event_function) { - if (typeof delete_event_function === 'function') { - let internal_eventname; - if (eventname in invidious_embed.eventname_table) { - internal_eventname = invidious_embed.eventname_table[eventname]; - } else if (invidious_embed.available_event_name.includes(eventname)) { - internal_eventname = eventname; - } else { - console.warn('removeEventListner cannot find such eventname : ' + eventname); - return; - } - - this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(listed_function => { - const allowFunctionDetected = listed_function.toString()[0] === '('; - if (allowFunctionDetected) { - listed_function.toString() !== delete_event_function.toString(); - } else { - listed_function !== delete_event_function; - } - }); - } else { - console.warn("removeEventListener secound args must be function"); - } - } - - /** - * return whether instance_origin origin can use or not - * @param {string} instance_origin - * @returns {Promise} - */ - async instance_access_check(instance_origin) { - let return_status; - const status_cahce_exist = instance_origin in invidious_embed.instance_status_list; - if (!status_cahce_exist) { - try { - const instance_stats = await fetch(instance_origin + '/api/v1/stats'); - if (instance_stats.ok) { - const instance_stats_json = await instance_stats.json(); - return_status = (instance_stats_json.software.name === 'invidious'); - } else { - return_status = false; - } - } catch { - return_status = false; - } - invidious_embed.instance_status_list[instance_origin] = return_status; - return return_status; - } else { - return invidious_embed.instance_status_list[instance_origin]; - } - } - - /** - * Need to use await - * - * Add invidious_embed.api_instance_list - * - * fetch from api.invidious.io - */ - async get_instance_list() { - invidious_embed.api_instance_list = []; - const instance_list_api = await (await fetch('https://api.invidious.io/instances.json?pretty=1&sort_by=type,users')).json(); - instance_list_api.forEach(instance_data => { - const http_check = instance_data[1]['type'] === 'https'; - let status_check_api_data; - if (instance_data[1]['monitor'] !== null) { - status_check_api_data = instance_data[1]['monitor']['statusClass'] === 'success'; - } - const api_available = instance_data[1]['api'] && instance_data[1]['cors']; - if (http_check && status_check_api_data && api_available) { - invidious_embed.api_instance_list.push(instance_data[1]['uri']); - } - }); - } - - /** - * Need to use await - * - * Auto select invidious instance and set invidious_embed.invidious_instance - */ - async auto_instance_select() { - if (await this.instance_access_check(invidious_embed.invidious_instance)) { - return; - } else { - if (invidious_embed.api_instance_list.length === 0) { - await this.get_instance_list(); - } - for (let x = 0; x < invidious_embed.api_instance_list.length; x++) { - if (await this.instance_access_check(invidious_embed.api_instance_list[x])) { - invidious_embed.invidious_instance = invidious_embed.api_instance_list[x]; - break; - } - } - } - } - - /** - * Need to use await - * Return videoData using invidious videos api - * @param {string} videoid - * @returns {Promise<{ + * @typedef {{ * title:string, * videoId:string, * videoThumbnails:[{ @@ -263,7 +144,138 @@ class invidious_embed { * viewCountText:string, * viewCount:number * }] - * }>} + * }} videoDataApi + */ + + /** + * @type {Object} + */ + static videodata_cahce = {}; + + /** + * Add event execute function for player + * @param {string} eventname + * @param {Function} event_execute_function + */ + addEventListener(eventname, event_execute_function) { + if (typeof event_execute_function === 'function') { + if (eventname in invidious_embed.eventname_table) { + this.eventobject[invidious_embed.eventname_table[eventname]].push(event_execute_function); + } else if (invidious_embed.available_event_name.includes(eventname)) { + this.eventobject[eventname].push(event_execute_function); + } else { + console.warn('addEventListener cannot find such eventname : ' + eventname); + } + } else { + console.warn("addEventListner secound args must be function"); + } + } + + /** + * remove spacific event execute function + * @param {string} eventname + * @param {Function} delete_event_function + */ + removeEventListener(eventname, delete_event_function) { + if (typeof delete_event_function === 'function') { + let internal_eventname; + if (eventname in invidious_embed.eventname_table) { + internal_eventname = invidious_embed.eventname_table[eventname]; + } else if (invidious_embed.available_event_name.includes(eventname)) { + internal_eventname = eventname; + } else { + console.warn('removeEventListner cannot find such eventname : ' + eventname); + return; + } + + this.eventobject[internal_eventname] = this.eventobject[internal_eventname].filter(listed_function => { + const allowFunctionDetected = listed_function.toString()[0] === '('; + if (allowFunctionDetected) { + listed_function.toString() !== delete_event_function.toString(); + } else { + listed_function !== delete_event_function; + } + }); + } else { + console.warn("removeEventListener secound args must be function"); + } + } + + /** + * return whether instance_origin origin can use or not + * @param {string} instance_origin + * @returns {Promise} + */ + async instance_access_check(instance_origin) { + let return_status; + const status_cahce_exist = instance_origin in invidious_embed.instance_status_list; + if (status_cahce_exist) { + return invidious_embed.instance_status_list[instance_origin]; + } + + try { + const instance_stats = await fetch(instance_origin + '/api/v1/stats'); + if (instance_stats.ok) { + const instance_stats_json = await instance_stats.json(); + return_status = (instance_stats_json.software.name === 'invidious'); + } else { + return_status = false; + } + } catch { + return_status = false; + } + invidious_embed.instance_status_list[instance_origin] = return_status; + return return_status; + } + + /** + * Need to use await + * + * Add invidious_embed.api_instance_list + * + * fetch from api.invidious.io + */ + async get_instance_list() { + invidious_embed.api_instance_list = []; + const instance_list_api = await (await fetch('https://api.invidious.io/instances.json?pretty=1&sort_by=type,users')).json(); + instance_list_api.forEach(instance_data => { + const http_check = instance_data[1]['type'] === 'https'; + let status_check_api_data; + if (instance_data[1]['monitor'] !== null) { + status_check_api_data = instance_data[1]['monitor']['statusClass'] === 'success'; + } + const api_available = instance_data[1]['api'] && instance_data[1]['cors']; + if (http_check && status_check_api_data && api_available) { + invidious_embed.api_instance_list.push(instance_data[1]['uri']); + } + }); + } + + /** + * Need to use await + * + * Auto select invidious instance and set invidious_embed.invidious_instance + */ + async auto_instance_select() { + if (await this.instance_access_check(invidious_embed.invidious_instance)) { + return; + } else { + if (invidious_embed.api_instance_list.length === 0) { + await this.get_instance_list(); + } + for (let x = 0; x < invidious_embed.api_instance_list.length; x++) { + if (await this.instance_access_check(invidious_embed.api_instance_list[x])) { + invidious_embed.invidious_instance = invidious_embed.api_instance_list[x]; + break; + } + } + } + } + + /** + * Return videoData using invidious videos api + * @param {string} videoid + * @returns {Promise} */ async videodata_api(videoid) { const not_in_videodata_cahce = !(videoid in invidious_embed.videodata_cahce); @@ -279,7 +291,6 @@ class invidious_embed { } /** - * Need to use await * check whether videoid exist or not * @param {string} videoid * @returns {promise} @@ -289,7 +300,6 @@ class invidious_embed { } /** - * Need to use await * return array of videoid in playlistid * @param {string} playlistid * @returns {Promise<[string]>} @@ -312,6 +322,8 @@ class invidious_embed { * @param {{ * videoId:string, * host:string, + * width:number, + * height:number, * playerVars:{ * start:number|string, * end:number|string, @@ -331,8 +343,30 @@ class invidious_embed { this.error_code = 0; this.volume = 100; this.loop = false; + + /** + * @type {[string]} + */ this.playlistVideoIds = []; + /** + * @type {{ + * ready:Function, + * ended:Function, + * error:Function, + * ratechange:Function, + * volumechange:Function, + * waiting:Function, + * timeupdate:Function, + * loadedmetadata:Function, + * play:Function, + * seeking:Function, + * seeked:Function, + * playerresize:Function, + * pause:Function, + * statechange:Function + * }} + */ this.eventobject = { ready: [], ended: [], @@ -357,7 +391,7 @@ class invidious_embed { } else if (typeof element === 'string') { replace_elemnt = document.getElementById(element); - if(replace_elemnt === null){ + if (replace_elemnt === null) { throw 'Can not find spacific element' } } else { @@ -479,11 +513,15 @@ class invidious_embed { return this; } + /** + * send message to iframe player + * @param {Object} data + */ postMessage(data) { - const additionalInfo = { - 'origin': location.origin, - 'widgetid': this.widgetid.toString(), - 'target': 'invidious_control' + const additionalInfo = { + 'origin': location.origin, + 'widgetid': this.widgetid.toString(), + 'target': 'invidious_control' }; data = Object.assign(additionalInfo, data); this.player_iframe.contentWindow.postMessage(data, this.target_origin); @@ -495,10 +533,10 @@ class invidious_embed { */ event_executor(eventname) { const execute_functions = this.eventobject[eventname]; - let return_data = { - type: eventname, - data: null, - target: this + let return_data = { + type: eventname, + data: null, + target: this }; switch (eventname) { case 'statechange': @@ -517,7 +555,7 @@ class invidious_embed { } /** - * + * recieve message from iframe player * @param {{ * data:{ * from:string, @@ -588,9 +626,9 @@ class invidious_embed { */ promise_send_event(event_name) { if (invidious_embed.api_promise) { - const promise_object = new Promise((resolve, reject) => this.message_wait[event_name].push(resolve) ); + const promise_object = new Promise((resolve, reject) => this.message_wait[event_name].push(resolve)); this.postMessage({ - eventname: event_name + eventname: event_name }); return promise_object; } else { @@ -812,7 +850,7 @@ class invidious_embed { } /** - * + * Internal function, so use such as loadVideoById() instead of this function. * @param {string|{ * videoId:string|undefined, * mediaContentUrl:string|undefined, @@ -1012,7 +1050,7 @@ class invidious_embed { } /** - * + * Internal function, so use such as loadPlaylist() instead of this function. * @param {string|[string]|{index:number|undefined,list:string,listType:string|undefined}} playlistData * @param {boolean} autoplay * @param {number} index @@ -1317,13 +1355,13 @@ class invidious_embed { */ async getVideoData() { const videoData = await this.videodata_api(this.videoId); - return { - video_id: this.videoId, - title: await this.promise_send_event('gettitle'), - list: await this.promise_send_event('getplaylistid'), - isListed: videoData.isListed, - isLive: videoData.liveNow, - isPremiere: videoData.premium + return { + video_id: this.videoId, + title: await this.promise_send_event('gettitle'), + list: await this.promise_send_event('getplaylistid'), + isListed: videoData.isListed, + isLive: videoData.liveNow, + isPremiere: videoData.premium }; } @@ -1386,7 +1424,7 @@ class invidious_embed { /** * After load iFrame api,function will execute * - * But this function always execute imidiretry because iframe api ready mean load complete this js file + * But this function always execute immediately because iframe api ready mean load complete this js file * @param {Function} func */ function invidious_ready(func) {