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] 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{}