2023-08-29 11:52:58 -04:00
class invidious _embed {
2023-08-17 22:40:50 -04:00
static widgetid = 0 ;
2023-09-24 10:04:09 -04:00
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'
] ;
2023-09-25 11:06:47 -04:00
/ * *
* Recive event response synchronization or asynchronous .
*
* Default false mean synchronization
* @ type { boolean }
* /
2023-08-17 22:40:50 -04:00
static api _promise = false ;
2023-08-29 11:52:58 -04:00
static invidious _instance = '' ;
2023-09-25 11:06:47 -04:00
/ * *
* @ type { [ string ] }
* /
2023-09-02 13:17:14 -04:00
static api _instance _list = [ ] ;
static instance _status _list = { } ;
2023-09-06 10:31:09 -04:00
static videodata _cahce = { } ;
2023-09-02 13:17:14 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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' ) {
2023-09-07 11:46:37 -04:00
if ( eventname in invidious _embed . eventname _table ) {
2023-09-25 11:06:47 -04:00
this . eventobject [ invidious _embed . eventname _table [ eventname ] ] . push ( event _execute _function ) ;
2023-09-07 11:46:37 -04:00
} else if ( invidious _embed . available _event _name . includes ( eventname ) ) {
2023-09-25 11:06:47 -04:00
this . eventobject [ eventname ] . push ( event _execute _function ) ;
2023-09-07 11:46:37 -04:00
} else {
console . warn ( 'addEventListener cannot find such eventname : ' + eventname ) ;
}
} else {
console . warn ( "addEventListner secound args must be function" ) ;
2023-09-02 13:17:14 -04:00
}
}
2023-09-25 11:06:47 -04:00
/ * *
* remove spacific event execute function
* @ param { string } eventname
* @ param { Function } delete _event _function
* /
removeEventListener ( eventname , delete _event _function ) {
if ( typeof delete _event _function === 'function' ) {
2023-09-07 11:46:37 -04:00
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 ;
}
2023-09-24 10:04:09 -04:00
this . eventobject [ internal _eventname ] = this . eventobject [ internal _eventname ] . filter ( listed _function => {
const allowFunctionDetected = listed _function . toString ( ) [ 0 ] === '(' ;
2023-09-07 11:46:37 -04:00
if ( allowFunctionDetected ) {
2023-09-25 11:06:47 -04:00
listed _function . toString ( ) !== delete _event _function . toString ( ) ;
2023-09-07 11:46:37 -04:00
} else {
2023-09-25 11:06:47 -04:00
listed _function !== delete _event _function ;
2023-09-07 11:46:37 -04:00
}
} ) ;
} else {
console . warn ( "removeEventListener secound args must be function" ) ;
2023-09-02 13:17:14 -04:00
}
}
2023-09-25 11:06:47 -04:00
/ * *
* return whether instance _origin origin can use or not
* @ param { string } instance _origin
* @ returns { Promise < boolean > }
* /
2023-09-02 13:17:14 -04:00
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 ) {
2023-09-02 13:27:05 -04:00
try {
2023-09-02 13:17:14 -04:00
const instance _stats = await fetch ( instance _origin + '/api/v1/stats' ) ;
if ( instance _stats . ok ) {
const instance _stats _json = await instance _stats . json ( ) ;
2023-09-23 05:13:54 -04:00
return _status = ( instance _stats _json . software . name === 'invidious' ) ;
2023-09-02 13:27:05 -04:00
} else {
2023-09-02 13:17:14 -04:00
return _status = false ;
}
2023-09-02 13:27:05 -04:00
} catch {
2023-09-02 13:17:14 -04:00
return _status = false ;
}
invidious _embed . instance _status _list [ instance _origin ] = return _status ;
return return _status ;
2023-09-02 13:27:05 -04:00
} else {
2023-09-02 13:17:14 -04:00
return invidious _embed . instance _status _list [ instance _origin ] ;
}
}
2023-09-25 11:06:47 -04:00
/ * *
* Need to use await
*
* Add invidious _embed . api _instance _list
*
* fetch from api . invidious . io
* /
2023-09-02 13:17:14 -04:00
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' ] ) ;
}
} ) ;
}
2023-09-25 11:06:47 -04:00
/ * *
* Need to use await
*
* Auto select invidious instance and set invidious _embed . invidious _instance
* /
2023-09-02 13:27:05 -04:00
async auto _instance _select ( ) {
if ( await this . instance _access _check ( invidious _embed . invidious _instance ) ) {
2023-09-02 13:17:14 -04:00
return ;
2023-09-02 13:27:05 -04:00
} else {
2023-09-02 13:17:14 -04:00
if ( invidious _embed . api _instance _list . length === 0 ) {
await this . get _instance _list ( ) ;
}
2023-09-02 13:27:05 -04:00
for ( let x = 0 ; x < invidious _embed . api _instance _list . length ; x ++ ) {
if ( await this . instance _access _check ( invidious _embed . api _instance _list [ x ] ) ) {
2023-09-02 13:17:14 -04:00
invidious _embed . invidious _instance = invidious _embed . api _instance _list [ x ] ;
break ;
}
}
2023-08-17 22:40:50 -04:00
}
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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
* } ]
* } > }
* /
2023-09-06 10:31:09 -04:00
async videodata _api ( videoid ) {
const not _in _videodata _cahce = ! ( videoid in invidious _embed . videodata _cahce ) ;
if ( not _in _videodata _cahce ) {
2023-09-24 07:15:38 -04:00
const video _api _response = await fetch ( invidious _embed . invidious _instance + "/api/v1/videos/" + videoid ) ;
2023-09-06 10:31:09 -04:00
if ( video _api _response . ok ) {
2023-09-07 11:46:37 -04:00
invidious _embed . videodata _cahce [ videoid ] = Object . assign ( { } , { status : true } , await video _api _response . json ( ) ) ;
2023-09-06 10:31:09 -04:00
} else {
2023-09-07 11:46:37 -04:00
invidious _embed . videodata _cahce [ videoid ] = { status : false } ;
2023-09-06 10:31:09 -04:00
}
}
return invidious _embed . videodata _cahce [ videoid ] ;
}
2023-09-25 11:06:47 -04:00
/ * *
* Need to use await
* check whether videoid exist or not
* @ param { string } videoid
* @ returns { promise < boolean > }
* /
2023-09-02 13:27:05 -04:00
async videoid _accessable _check ( videoid ) {
2023-09-06 10:31:09 -04:00
return ( await this . videodata _api ( videoid ) ) . status ;
2023-09-02 13:17:14 -04:00
}
2023-09-25 11:06:47 -04:00
/ * *
* Need to use await
* return array of videoid in playlistid
* @ param { string } playlistid
* @ returns { Promise < [ string ] > }
* /
2023-09-02 13:17:14 -04:00
async getPlaylistVideoids ( playlistid ) {
const playlist _api _response = await fetch ( invidious _embed . invidious _instance + "/api/v1/playlists/" + playlistid ) ;
2023-09-02 13:27:05 -04:00
if ( playlist _api _response . ok ) {
2023-09-02 13:17:14 -04:00
const playlist _api _json = await playlist _api _response . json ( ) ;
let tmp _videoid _list = [ ] ;
2023-09-02 13:27:05 -04:00
playlist _api _json . videos . forEach ( videodata => tmp _videoid _list . push ( videodata . videoId ) ) ;
2023-09-02 13:17:14 -04:00
return tmp _videoid _list ;
2023-09-02 13:27:05 -04:00
} else {
2023-09-02 13:17:14 -04:00
return [ ] ;
2023-08-17 22:40:50 -04:00
}
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
*
* @ 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
* /
2023-09-02 13:17:14 -04:00
async Player ( element , options ) {
2023-08-17 22:40:50 -04:00
this . player _status = - 1 ;
this . error _code = 0 ;
2023-08-25 11:48:41 -04:00
this . volume = 100 ;
2023-09-06 10:31:09 -04:00
this . loop = false ;
this . playlistVideoIds = [ ] ;
2023-09-23 05:13:54 -04:00
this . eventobject = {
ready : [ ] ,
ended : [ ] ,
error : [ ] ,
ratechange : [ ] ,
volumechange : [ ] ,
waiting : [ ] ,
timeupdate : [ ] ,
loadedmetadata : [ ] ,
play : [ ] ,
seeking : [ ] ,
seeked : [ ] ,
playerresize : [ ] ,
pause : [ ] ,
statechange : [ ]
} ;
2023-08-29 11:52:58 -04:00
let replace _elemnt ;
2023-09-07 11:46:37 -04:00
this . isPlaylistVideoList = false ;
2023-08-29 11:52:58 -04:00
if ( element === undefined || element === null ) {
2023-08-28 18:56:52 -04:00
throw 'Please, pass element id or HTMLElement as first argument' ;
2023-09-02 13:27:05 -04:00
} else if ( typeof element === 'string' ) {
2023-08-17 22:40:50 -04:00
replace _elemnt = document . getElementById ( element ) ;
2023-09-25 11:06:47 -04:00
if ( replace _elemnt === null ) {
throw 'Can not find spacific element'
}
2023-09-02 13:27:05 -04:00
} else {
2023-08-17 22:40:50 -04:00
replace _elemnt = element ;
}
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
let iframe _src = '' ;
if ( options . host !== undefined && options . host !== "" ) {
2023-08-17 22:40:50 -04:00
iframe _src = new URL ( options . host ) . origin ;
2023-09-02 13:27:05 -04:00
} else if ( invidious _embed . invidious _instance !== '' ) {
2023-08-29 11:52:58 -04:00
iframe _src = invidious _embed . invidious _instance ;
}
2023-09-24 10:04:09 -04:00
2023-09-02 13:17:14 -04:00
if ( ! await this . instance _access _check ( iframe _src ) ) {
await this . auto _instance _select ( ) ;
iframe _src = invidious _embed . invidious _instance ;
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-09-02 13:17:14 -04:00
invidious _embed . invidious _instance = iframe _src ;
this . target _origin = iframe _src ;
2023-08-17 22:40:50 -04:00
iframe _src += '/embed/' ;
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( typeof options . videoId === 'string' && options . videoId . length === 11 ) {
2023-08-17 22:40:50 -04:00
iframe _src += options . videoId ;
this . videoId = options . videoId ;
2023-09-02 13:17:14 -04:00
if ( ! await this . videoid _accessable _check ( options . videoId ) ) {
this . error _code = 100 ;
this . event _executor ( 'error' ) ;
return ;
}
2023-09-02 13:27:05 -04:00
} else {
2023-08-17 22:40:50 -04:00
this . error _code = 2 ;
this . event _executor ( 'error' ) ;
2023-08-29 11:27:32 -04:00
return ;
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
let search _params = new URLSearchParams ( '' ) ;
search _params . append ( 'widgetid' , invidious _embed . widgetid ) ;
2023-08-17 22:40:50 -04:00
this . widgetid = invidious _embed . widgetid ;
invidious _embed . widgetid ++ ;
2023-08-29 11:52:58 -04:00
search _params . append ( 'origin' , location . origin ) ;
search _params . append ( 'enablejsapi' , '1' ) ;
2023-09-24 10:04:09 -04:00
2023-09-06 10:31:09 -04:00
let no _start _parameter = true ;
2023-08-29 11:52:58 -04:00
if ( typeof options . playerVars === 'object' ) {
2023-08-17 22:40:50 -04:00
this . option _playerVars = options . playerVars ;
2023-09-07 11:46:37 -04:00
Object . keys ( options . playerVars ) . forEach ( key => {
2023-09-06 10:54:18 -04:00
if ( typeof key === 'string' ) {
let keyValue = options . playerVars [ key ] ;
switch ( typeof keyValue ) {
case 'number' :
keyValue = keyValue . toString ( ) ;
break ;
case 'string' :
break ;
2023-09-07 11:46:37 -04:00
default :
2023-09-06 10:54:18 -04:00
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' ) ;
2023-08-17 22:40:50 -04:00
}
2023-09-07 11:46:37 -04:00
} ) ;
2023-09-24 10:04:09 -04:00
2023-09-06 10:31:09 -04:00
if ( options . playerVars . start !== undefined ) {
no _start _parameter = false ;
}
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( options . playerVars . autoplay === undefined ) {
search _params . append ( 'autoplay' , '0' ) ;
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-09-06 10:31:09 -04:00
} else {
search _params . append ( 'autoplay' , '0' ) ;
}
2023-09-24 10:04:09 -04:00
2023-09-06 10:31:09 -04:00
if ( no _start _parameter ) {
search _params . append ( 'start' , '0' ) ;
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-08-17 22:40:50 -04:00
iframe _src += "?" + search _params . toString ( ) ;
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( typeof options . events === 'object' ) {
2023-09-07 11:46:37 -04:00
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' ) ;
}
} ) ;
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-08-17 22:40:50 -04:00
this . player _iframe = document . createElement ( "iframe" ) ;
this . loaded = false ;
2023-09-02 13:17:14 -04:00
this . addEventListener ( 'loadedmetadata' , ( ) => { this . event _executor ( 'ready' ) ; this . loaded = true ; } ) ;
this . addEventListener ( 'loadedmetadata' , ( ) => { this . setVolume ( this . volume ) ; } ) ;
2023-09-07 11:46:37 -04:00
this . addEventListener ( 'ended' , ( ) => { if ( this . isPlaylistVideoList ) { this . nextVideo ( ) } } )
2023-08-17 22:40:50 -04:00
this . player _iframe . src = iframe _src ;
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( typeof options . width === 'number' ) {
2023-08-17 22:40:50 -04:00
this . player _iframe . width = options . width ;
2023-09-02 13:27:05 -04:00
} else {
2023-09-06 10:54:18 -04:00
this . player _iframe . width = 640 ;
this . player _iframe . style . maxWidth = '100%' ;
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-09-07 11:46:37 -04:00
if ( typeof options . height === 'number' ) {
this . player _iframe . height = options . height ;
2023-09-02 13:27:05 -04:00
} else {
2023-08-29 11:52:58 -04:00
this . player _iframe . height = this . player _iframe . width * ( 9 / 16 ) ;
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-08-17 22:40:50 -04:00
this . player _iframe . style . border = "none" ;
replace _elemnt . replaceWith ( this . player _iframe ) ;
2023-09-25 11:06:47 -04:00
/ * *
* @ type { Object . < string , string > }
* /
2023-08-17 22:40:50 -04:00
this . eventdata = { } ;
return this ;
}
2023-08-29 11:52:58 -04:00
postMessage ( data ) {
2023-09-24 10:04:09 -04:00
const additionalInfo = {
'origin' : location . origin ,
'widgetid' : this . widgetid . toString ( ) ,
'target' : 'invidious_control'
} ;
2023-08-29 11:52:58 -04:00
data = Object . assign ( additionalInfo , data ) ;
this . player _iframe . contentWindow . postMessage ( data , this . target _origin ) ;
}
2023-09-25 11:06:47 -04:00
/ * *
* execute eventname event
* @ param { string } eventname
* /
2023-08-29 11:52:58 -04:00
event _executor ( eventname ) {
2023-09-07 11:46:37 -04:00
const execute _functions = this . eventobject [ eventname ] ;
2023-09-24 10:04:09 -04:00
let return _data = {
type : eventname ,
data : null ,
target : this
} ;
2023-09-07 11:46:37 -04:00
switch ( eventname ) {
case 'statechange' :
return _data . data = this . getPlayerState ( ) ;
break ;
case 'error' :
return _data . data = this . error _code ;
}
execute _functions . forEach ( func => {
try {
func ( return _data ) ;
} catch ( e ) {
console . error ( e ) ;
}
} ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
*
* @ param { {
* data : {
* from : string ,
* message _kind : string ,
* widgetid : string ,
* command : string ,
* value : string | number | object | null ,
* eventname : string
* }
* } } message
* /
2023-08-29 11:52:58 -04:00
receiveMessage ( message ) {
2023-09-07 11:46:37 -04:00
const onControlAndHasWidgetId = message . data . from === 'invidious_control' && message . data . widgetid === this . widgetid . toString ( ) ;
2023-09-06 10:54:18 -04:00
if ( onControlAndHasWidgetId ) {
2023-08-29 11:52:58 -04:00
switch ( message . data . message _kind ) {
2023-08-17 22:40:50 -04:00
case 'info_return' :
2023-08-29 11:52:58 -04:00
const promise _array = this . message _wait [ message . data . command ] ;
promise _array . forEach ( element => {
if ( message . data . command === 'getvolume' ) {
element ( message . data . value * 100 ) ;
2023-09-02 13:27:05 -04:00
} else {
2023-08-29 11:52:58 -04:00
element ( message . data . value ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
} ) ;
2023-08-17 22:40:50 -04:00
this . message _wait [ message . data . command ] = [ ] ;
break ;
case 'event' :
2023-08-29 11:26:20 -04:00
if ( typeof message . data . eventname === 'string' ) {
2023-08-17 22:40:50 -04:00
this . event _executor ( message . data . eventname ) ;
2023-08-29 11:52:58 -04:00
const previous _status = this . player _status ;
switch ( message . data . eventname ) {
2023-08-17 22:40:50 -04:00
case 'ended' :
this . player _status = 0 ;
break ;
case 'play' :
this . player _status = 1 ;
break ;
case 'timeupdate' :
this . player _status = 1 ;
2023-08-29 11:52:58 -04:00
this . eventdata = Object . assign ( { } , this . eventdata , message . data . value ) ;
2023-08-17 22:40:50 -04:00
break ;
case 'pause' :
this . player _status = 2 ;
break ;
case 'waiting' :
this . player _status = 3 ;
break ;
case 'loadedmetadata' :
2023-08-29 11:52:58 -04:00
this . eventdata = Object . assign ( { } , this . eventdata , message . data . value ) ;
2023-08-17 22:40:50 -04:00
break ;
}
2023-08-29 11:52:58 -04:00
if ( previous _status !== this . player _status ) {
2023-08-17 22:40:50 -04:00
this . event _executor ( 'statechange' ) ;
}
}
}
}
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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 < number > | Promise < boolean > | Promise < [ number ] > | Promise < string > }
* /
2023-08-29 11:52:58 -04:00
promise _send _event ( event _name ) {
if ( invidious _embed . api _promise ) {
2023-09-24 10:04:09 -04:00
const promise _object = new Promise ( ( resolve , reject ) => this . message _wait [ event _name ] . push ( resolve ) ) ;
this . postMessage ( {
eventname : event _name
} ) ;
2023-08-29 11:52:58 -04:00
return promise _object ;
2023-09-02 13:27:05 -04:00
} else {
2023-08-17 22:40:50 -04:00
return this . eventdata [ event _name ] ;
}
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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;
* /
2023-08-29 11:52:58 -04:00
getPlayerState ( ) {
2023-08-17 22:40:50 -04:00
return this . player _status ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* send play command to iframe player
* @ example
* player . playVideo ( ) ;
* /
2023-08-29 11:52:58 -04:00
playVideo ( ) {
this . postMessage ( { eventname : 'play' } ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* send pause command to iframe player
* @ example
* player . pauseVideo ( ) ;
* /
2023-08-29 11:52:58 -04:00
pauseVideo ( ) {
this . postMessage ( { eventname : 'pause' } ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* Default return number range 0 to 100
*
* But if set invidious _embed . api _promise true , return Promise < number >
* @ returns { number | Promise < number > }
* @ example
* const volume = player . getVolume ( ) ; //invidious_embed.api_promise is false
* const volume = await player . getVolume ( ) ; //invidious_embed.api_promise is true
* //volume = 100
* /
2023-08-29 11:52:58 -04:00
getVolume ( ) {
2023-08-17 22:40:50 -04:00
return this . promise _send _event ( 'getvolume' ) ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* Send set volume event to iframe player
*
* volume must be range 0 to 100
* @ param { number } volume
* @ example
* player . setVolume ( 50 ) ; //set volume 50%
* /
2023-08-29 11:52:58 -04:00
setVolume ( volume ) {
if ( typeof volume === 'number' ) {
this . volume = volume ;
2023-09-02 20:04:04 -04:00
if ( volume !== NaN && volume >= 0 && volume <= 100 ) {
2023-08-29 11:52:58 -04:00
this . postMessage ( { eventname : 'setvolume' , value : volume / 100 } ) ;
}
2023-09-02 13:27:05 -04:00
} else {
2023-08-29 11:52:58 -04:00
console . warn ( "setVolume first argument must be number" ) ;
2023-08-17 22:40:50 -04:00
}
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* Get player iframe node
* @ returns { Node }
* @ example
* const invidious _player _node = player . getIframe ( ) ;
* /
2023-08-29 11:52:58 -04:00
getIframe ( ) {
2023-08-17 22:40:50 -04:00
return this . player _iframe ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* delete player iframe
* @ example
* player . destroy ( ) ;
* /
2023-08-29 11:52:58 -04:00
destroy ( ) {
2023-08-17 22:40:50 -04:00
this . player _iframe . remove ( ) ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* send mute event to iframe player
* @ example
* player . mute ( ) ;
* /
2023-08-29 11:52:58 -04:00
mute ( ) {
this . postMessage ( { eventname : 'setmutestatus' , value : true } ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* send unmute event to iframe player
* @ example
* player . unMute ( ) ;
* /
2023-08-29 11:52:58 -04:00
unMute ( ) {
this . postMessage ( { eventname : 'setmutestatus' , value : false } ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* Whether mute or not .
*
* Default return boolean .
*
* But if set invidious _embed . api _promise true , return Promise < boolean > .
* @ returns { boolean | Promise < boolean > }
* @ example
* const muteStatus = player . isMuted ( ) ; //invidious_embed.api_promise false
* const muteStatus = await player . isMuted ( ) ; //invidious_embed.api_promise true
* //muteStatus = false
* /
2023-08-29 11:52:58 -04:00
isMuted ( ) {
2023-08-17 22:40:50 -04:00
return this . promise _send _event ( 'getmutestatus' ) ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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 ) {
2023-09-02 13:17:14 -04:00
if ( typeof seconds === 'number' ) {
if ( seconds !== NaN && seconds !== undefined ) {
this . postMessage ( { eventname : 'seek' , value : seconds } ) ;
}
2023-09-02 13:27:05 -04:00
} else {
2023-09-02 13:17:14 -04:00
console . warn ( 'seekTo first argument type must be number' )
2023-08-17 22:40:50 -04:00
}
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* set iframe size
* @ param { number } width
* @ param { number } height
* @ example
* player . setSize ( 480 , 270 ) ;
* /
setSize ( width , height ) {
2023-09-02 13:27:05 -04:00
if ( typeof width === 'number' && typeof height === 'number' ) {
2023-09-02 13:17:14 -04:00
this . player _iframe . width = width ;
this . player _iframe . height = height ;
2023-09-02 13:27:05 -04:00
} else {
2023-09-02 13:17:14 -04:00
console . warn ( 'setSize first and secound argument type must be number' ) ;
}
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* get playback rate .
*
* Default return number .
*
* But if set invidious _embed . api _promise true , return Promise < number > .
* @ returns { number | Promise < number > }
* @ 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
* /
2023-08-29 11:52:58 -04:00
getPlaybackRate ( ) {
2023-08-17 22:40:50 -04:00
return this . promise _send _event ( 'getplaybackrate' ) ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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 ) {
2023-09-02 13:27:05 -04:00
if ( typeof suggestedRate === 'number' ) {
2023-09-02 13:17:14 -04:00
if ( suggestedRate !== NaN ) {
this . postMessage ( { eventname : 'setplaybackrate' , value : suggestedRate } ) ;
2023-09-02 13:27:05 -04:00
} else {
2023-09-02 13:17:14 -04:00
console . warn ( 'setPlaybackRate first argument NaN is no valid' ) ;
}
2023-09-02 13:27:05 -04:00
} else {
2023-09-02 13:17:14 -04:00
console . warn ( 'setPlaybackRate first argument type must be number' ) ;
2023-08-17 22:40:50 -04:00
}
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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];
* /
2023-08-29 11:52:58 -04:00
getAvailablePlaybackRates ( ) {
2023-08-17 22:40:50 -04:00
return this . promise _send _event ( 'getavailableplaybackrates' ) ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
*
* @ param { string | {
* videoId : string | undefined ,
* mediaContentUrl : string | undefined ,
* startSeconds : number ,
* endSeconds : number
* } } option
* @ param { boolean } autoplay
* @ param { number | undefined } startSeconds _arg
* @ param { Object . < string , string > } additional _argument
* @ returns
* /
2023-09-06 10:31:09 -04:00
async playOtherVideoById ( option , autoplay , startSeconds _arg , additional _argument ) { //internal fuction
2023-08-17 22:40:50 -04:00
let videoId = '' ;
2023-08-29 11:52:58 -04:00
let startSeconds = 0 ;
2023-08-17 22:40:50 -04:00
let endSeconds = - 1 ;
let mediaContetUrl = '' ;
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( typeof option === 'string' ) {
if ( option . length === 11 ) {
2023-09-24 10:04:09 -04:00
videoId = option ;
2023-09-02 13:27:05 -04:00
} else {
2023-08-17 22:40:50 -04:00
mediaContetUrl = option ;
}
2023-09-24 10:04:09 -04:00
2023-08-29 11:26:20 -04:00
if ( typeof startSeconds _arg === 'number' ) {
2023-08-17 22:40:50 -04:00
startSeconds = startSeconds _arg ;
}
2023-09-02 13:27:05 -04:00
} else if ( typeof option === 'object' ) {
2023-08-29 11:52:58 -04:00
if ( typeof option . videoId === 'string' ) {
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( option . videoId . length == 11 ) {
2023-08-17 22:40:50 -04:00
videoId = option . videoId ;
2023-09-02 13:27:05 -04:00
} else {
2023-08-17 22:40:50 -04:00
this . error _code = 2 ;
this . event _executor ( 'error' ) ;
2023-08-29 11:52:58 -04:00
return ;
2023-08-17 22:40:50 -04:00
}
2023-09-02 13:27:05 -04:00
} else if ( typeof option . mediaContentUrl === 'string' ) {
2023-08-17 22:40:50 -04:00
mediaContetUrl = option . mediaContentUrl ;
2023-09-02 13:27:05 -04:00
} else {
2023-08-17 22:40:50 -04:00
this . error _code = 2 ;
this . event _executor ( 'error' ) ;
2023-08-29 11:52:58 -04:00
return ;
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( typeof option . startSeconds === 'number' && option . startSeconds >= 0 ) {
2023-08-17 22:40:50 -04:00
startSeconds = option . startSeconds ;
}
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( typeof option . endSeconds === 'number' && option . endSeconds >= 0 ) {
2023-08-25 11:48:41 -04:00
endSeconds = option . endSeconds ;
2023-08-17 22:40:50 -04:00
}
}
2023-08-29 11:52:58 -04:00
if ( mediaContetUrl . length > 0 ) {
2023-09-07 11:46:37 -04:00
const match _result = mediaContetUrl . match ( /\/([A-Za-z0-9]{11})/ ) ;
2023-09-02 13:27:05 -04:00
if ( match _result !== null && match _result . length === 2 ) {
2023-09-02 13:17:14 -04:00
videoId = match _result [ 1 ] ;
2023-09-02 13:27:05 -04:00
} else {
2023-08-17 22:40:50 -04:00
this . error _code = 2 ;
this . event _executor ( 'error' ) ;
2023-08-29 11:52:58 -04:00
return ;
2023-08-17 22:40:50 -04:00
}
}
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
let iframe _sorce = this . target _origin . slice ( ) ;
2023-08-17 22:40:50 -04:00
iframe _sorce += "/embed/" + videoId ;
this . videoId = videoId ;
2023-09-24 10:04:09 -04:00
2023-09-02 13:17:14 -04:00
if ( ! await this . videoid _accessable _check ( videoId ) ) {
this . error _code = 100 ;
this . event _executor ( 'error' ) ;
return ;
}
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
let search _params = new URLSearchParams ( '' ) ;
search _params . append ( 'origin' , location . origin ) ;
search _params . append ( 'enablejsapi' , '1' ) ;
search _params . append ( 'widgetid' , invidious _embed . widgetid ) ;
2023-08-17 22:40:50 -04:00
this . widgetid = invidious _embed . widgetid ;
invidious _embed . widgetid ++ ;
2023-08-29 11:40:03 -04:00
search _params . append ( 'autoplay' , Number ( autoplay ) ) ;
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( this . option _playerVars !== undefined ) {
2023-09-06 10:31:09 -04:00
const ignore _keys = [ 'autoplay' , 'start' , 'end' , 'index' , 'list' ] ;
2023-08-29 11:52:58 -04:00
Object . keys ( this . option _playerVars ) . forEach ( key => {
2023-09-06 10:31:09 -04:00
if ( ! ignore _keys . includes ( key ) ) {
2023-08-29 11:52:58 -04:00
search _params . append ( key , this . option _playerVars [ key ] ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
} )
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-09-06 10:31:09 -04:00
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 ] ) ;
}
} )
2023-08-17 22:40:50 -04:00
}
2023-09-24 10:04:09 -04:00
2023-09-06 10:31:09 -04:00
search _params . append ( 'start' , startSeconds ) ;
2023-08-29 11:52:58 -04:00
if ( endSeconds !== - 1 && endSeconds >= 0 ) {
if ( endSeconds > startSeconds ) {
search _params . append ( 'end' , endSeconds ) ;
2023-09-02 13:27:05 -04:00
} else {
2023-08-29 11:52:58 -04:00
throw 'Invalid end seconds because end seconds before start seconds' ;
2023-08-17 22:40:50 -04:00
}
}
2023-09-24 10:04:09 -04:00
2023-08-17 22:40:50 -04:00
iframe _sorce += "?" + search _params . toString ( ) ;
this . player _iframe . src = iframe _sorce ;
2023-09-24 10:04:09 -04:00
2023-08-29 11:52:58 -04:00
if ( autoplay ) {
2023-08-17 22:40:50 -04:00
this . player _status = 5 ;
}
this . eventdata = { } ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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
* /
2023-08-29 11:52:58 -04:00
loadVideoById ( option , startSeconds ) {
2023-09-07 11:46:37 -04:00
this . isPlaylistVideoList = false ;
2023-09-06 10:31:09 -04:00
this . playOtherVideoById ( option , true , startSeconds , { } ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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
* /
2023-08-29 11:52:58 -04:00
cueVideoById ( option , startSeconds ) {
2023-09-07 11:46:37 -04:00
this . isPlaylistVideoList = false ;
2023-09-06 10:31:09 -04:00
this . playOtherVideoById ( option , false , startSeconds , { } ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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
* /
2023-08-29 11:52:58 -04:00
cueVideoByUrl ( option , startSeconds ) {
2023-09-07 11:46:37 -04:00
this . isPlaylistVideoList = false ;
2023-09-06 10:31:09 -04:00
this . playOtherVideoById ( option , false , startSeconds , { } ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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
* /
2023-08-29 11:52:58 -04:00
loadVideoByUrl ( option , startSeconds ) {
2023-09-07 11:46:37 -04:00
this . isPlaylistVideoList = false ;
2023-09-06 10:31:09 -04:00
this . playOtherVideoById ( option , true , startSeconds , { } ) ;
}
2023-09-25 11:06:47 -04:00
/ * *
*
* @ param { string | [ string ] | { index : number | undefined , list : string , listType : string | undefined } } playlistData
* @ param { boolean } autoplay
* @ param { number } index
* @ param { number } startSeconds
* /
2023-09-07 11:46:37 -04:00
async playPlaylist ( playlistData , autoplay , index , startSeconds ) {
2023-09-25 11:06:47 -04:00
/ * *
* @ type { string }
* /
2023-09-06 10:31:09 -04:00
let playlistId ;
if ( typeof playlistData === 'string' ) {
2023-09-07 11:46:37 -04:00
this . playlistVideoIds = [ playlistData ] ;
this . isPlaylistVideoList = true ;
2023-09-06 10:31:09 -04:00
} else if ( typeof playlistData === 'object' ) {
if ( Array . isArray ( playlistData ) ) {
this . playlistVideoIds = playlistData ;
2023-09-07 11:46:37 -04:00
this . isPlaylistVideoList = true ;
2023-09-06 10:31:09 -04:00
} else {
index = playlistData [ 'index' ] ;
let listType = 'playlist' ;
2023-09-07 11:46:37 -04:00
if ( typeof playlistData [ 'listType' ] === 'string' ) {
2023-09-06 10:31:09 -04:00
listType = playlistData [ 'listType' ] ;
}
2023-09-24 10:04:09 -04:00
2023-09-06 10:31:09 -04:00
switch ( listType ) {
case 'playlist' :
2023-09-07 11:46:37 -04:00
if ( typeof playlistData [ 'list' ] === 'string' ) {
2023-09-06 10:31:09 -04:00
this . playlistVideoIds = await this . getPlaylistVideoids ( playlistData [ 'list' ] ) ;
playlistId = playlistData [ 'list' ] ;
} else {
2023-09-07 11:46:37 -04:00
console . error ( 'playlist data list must be string' ) ;
2023-09-06 10:31:09 -04:00
return ;
}
break ;
case 'user_uploads' :
console . warn ( 'sorry user_uploads not support' ) ;
return ;
default :
console . error ( 'listType : ' + listType + ' is unknown' ) ;
return ;
}
}
2023-09-24 10:04:09 -04:00
2023-09-07 11:46:37 -04:00
if ( typeof playlistData . startSeconds === 'number' ) {
startSeconds = playlistData . startSeconds ;
}
2023-09-06 10:31:09 -04:00
} else {
console . error ( 'playlist function first argument must be string or array of string' ) ;
return ;
}
2023-09-24 10:04:09 -04:00
2023-09-07 11:46:37 -04:00
if ( this . playlistVideoIds . length === 0 ) {
2023-09-06 10:31:09 -04:00
console . error ( 'playlist length 0 is invalid' ) ;
return ;
}
2023-09-07 11:46:37 -04:00
let parameter = { index : 0 } ;
2023-09-06 10:31:09 -04:00
if ( typeof index === 'undefined' ) {
index = 0 ;
2023-09-07 11:46:37 -04:00
} else if ( typeof index === 'number' ) {
2023-09-06 10:31:09 -04:00
parameter . index = index ;
2023-09-07 11:46:37 -04:00
} else {
2023-09-06 10:31:09 -04:00
console . error ( 'index must be number of undefined' ) ;
}
2023-09-24 10:04:09 -04:00
2023-09-07 11:46:37 -04:00
if ( typeof playlistId === 'string' ) {
2023-09-06 10:31:09 -04:00
parameter [ 'list' ] = playlistId ;
this . playlistId = playlistId ;
}
2023-09-07 11:46:37 -04:00
this . sub _index = parameter . index ;
2023-09-24 10:04:09 -04:00
2023-09-07 11:46:37 -04:00
if ( index >= this . playlistVideoIds . length ) {
index = 0 ;
parameter . index = 0 ;
}
this . playOtherVideoById ( this . playlistVideoIds [ index ] , autoplay , startSeconds , parameter ) ;
2023-09-06 10:31:09 -04:00
}
2023-09-25 11:06:47 -04:00
/ * *
* 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
* /
2023-09-07 11:46:37 -04:00
cuePlaylist ( data , index , startSeconds ) {
this . playPlaylist ( data , false , index , startSeconds ) ;
2023-09-06 10:31:09 -04:00
}
2023-09-25 11:06:47 -04:00
/ * *
* 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
* /
2023-09-07 11:46:37 -04:00
loadPlaylist ( data , index , startSeconds ) {
this . playPlaylist ( data , true , index , startSeconds ) ;
2023-09-06 10:31:09 -04:00
}
2023-09-25 11:06:47 -04:00
/ * *
* Play video spacific number of index
* @ param { number } index count start with 0
* @ example
* player . playVideoAt ( 5 ) ; //play video playlist index 6th
* /
2023-09-06 10:31:09 -04:00
playVideoAt ( index ) {
if ( typeof index === 'number' ) {
2023-09-07 11:46:37 -04:00
let parameter = { index : index } ;
2023-09-06 10:31:09 -04:00
if ( this . playlistId !== undefined ) {
parameter [ 'list' ] = this . playlistId ;
}
2023-09-07 11:46:37 -04:00
this . playOtherVideoById ( this . playlistVideoIds [ index ] , true , 0 , parameter ) ;
2023-09-06 10:31:09 -04:00
} else {
console . error ( 'playVideoAt first argument must be number' ) ;
}
}
2023-09-25 11:06:47 -04:00
/ * *
* play next video of playlist
*
* if end of playlist , if loop is true , load first video of playlist .
* @ example
* player . nextVideo ( ) ;
* /
2023-09-06 10:31:09 -04:00
async nextVideo ( ) {
let now _index = this . promise _send _event ( 'getplaylistindex' ) ;
2023-09-07 11:46:37 -04:00
if ( now _index === null ) {
now _index = this . sub _index ;
}
2023-09-24 10:04:09 -04:00
2023-09-07 11:46:37 -04:00
if ( now _index === this . playlistVideoIds . length - 1 ) {
2023-09-06 10:31:09 -04:00
if ( this . loop ) {
now _index = 0 ;
} else {
console . log ( 'end of playlist' ) ;
return ;
}
} else {
now _index ++ ;
}
2023-09-07 11:46:37 -04:00
this . sub _index = now _index ;
let parameter = { index : now _index } ;
2023-09-06 10:31:09 -04:00
if ( this . playlistId !== undefined ) {
parameter [ 'list' ] = this . playlistId ;
}
2023-09-07 11:46:37 -04:00
this . playOtherVideoById ( this . playlistVideoIds [ now _index ] , true , 0 , parameter ) ;
2023-09-06 10:31:09 -04:00
}
2023-09-25 11:06:47 -04:00
/ * *
* play previous video of playlist
*
* if start of playlist , if loop is true , load end video of playlist .
* @ example
* player . previousVideo ( ) ;
* /
2023-09-06 10:31:09 -04:00
async previousVideo ( ) {
let now _index = this . promise _send _event ( 'getplaylistindex' ) ;
2023-09-07 11:46:37 -04:00
if ( now _index === null ) {
now _index = this . sub _index ;
}
2023-09-06 10:31:09 -04:00
if ( now _index === 0 ) {
if ( this . loop ) {
2023-09-07 11:46:37 -04:00
now _index = this . playlistVideoIds . length - 1 ;
2023-09-06 10:31:09 -04:00
} else {
console . log ( 'back to start of playlist' ) ;
return ;
}
} else {
now _index -- ;
}
2023-09-07 11:46:37 -04:00
this . sub _index = now _index ;
let parameter = { index : now _index } ;
2023-09-06 10:31:09 -04:00
if ( this . playlistId !== undefined ) {
parameter [ 'list' ] = this . playlistId ;
}
2023-09-07 11:46:37 -04:00
this . playOtherVideoById ( this . playlistVideoIds [ now _index ] , true , 0 , parameter ) ;
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* Get dulation of video
*
* Default return number
*
* But if set invidious _embed . api _promise true , return Promise < number > .
* @ returns { number | Promise < number > }
* @ 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
* /
2023-08-29 11:52:58 -04:00
getDuration ( ) {
2023-08-17 22:40:50 -04:00
return this . promise _send _event ( 'getduration' ) ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* Get url of loaded video
* @ returns { string }
* @ example
* const video _url = player . getVideoUrl ( ) ;
* //video_url = "https://yewtu.be/watch?v=KqE7Bwhd-rE"
* /
2023-08-29 11:52:58 -04:00
getVideoUrl ( ) {
2023-08-17 22:40:50 -04:00
return this . target _origin + "/watch?v=" + this . videoId ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* Get title of loaded video .
*
* Default return string
*
* But if set invidious _embed . api _promise true , return Promise < string > .
* @ returns { string , Promise < string > }
* @ example
* const title = player . getTitle ( ) ; //invidious_embed.api_promise is false
* const title = await player . getTitle ( ) ; //invidious_embed.api_promise is true
* //title = "【夏の終わりに】夏祭り/ときのそら【歌ってみた】"
* /
2023-09-24 10:04:09 -04:00
getTitle ( ) {
2023-09-24 07:15:38 -04:00
return this . promise _send _event ( 'gettitle' ) ;
}
2023-09-25 11:06:47 -04:00
/ * *
* Get video embed iframe string
*
* Default return string
*
* But if set invidious _embed . api _promise true , return Promise < string > .
* @ returns { string , Promise < string > }
* @ 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 = `<iframe width="560" height="315" src="https://yewtu.be/embed/KqE7Bwhd-rE" title="【夏の終わりに】夏祭り/ときのそら【歌ってみた】" frameborder="0" allow="autoplay;encrypted-media;picture-in-picture;web-share" allowfullscreen></iframe>`
* /
2023-09-24 07:15:38 -04:00
getVideoEmbedCode ( ) {
const embed _url = encodeURI ( ` ${ this . target _origin } /embed/ ${ this . videoId } ` ) ;
2023-09-24 10:04:09 -04:00
const html _escape = ( html ) => {
const html _escaped = html . replace ( /[&'`"<>]/g , match => {
2023-09-24 07:15:38 -04:00
return {
'&' : '&' ,
"'" : ''' ,
'`' : '`' ,
'"' : '"' ,
'<' : '<' ,
'>' : '>' ,
2023-09-24 10:04:09 -04:00
} [ match ]
} ) ;
2023-09-24 07:15:38 -04:00
return html _escaped ;
}
2023-09-24 10:04:09 -04:00
const iframe _constractor = ( raw _title ) => {
2023-09-24 07:15:38 -04:00
const html _escaped _title = html _escape ( raw _title ) ;
2023-09-24 10:04:09 -04:00
return ` <iframe width="560" height="315" src=" ${ embed _url } " title=" ${ html _escaped _title } " frameborder="0" allow="autoplay;encrypted-media;picture-in-picture;web-share" allowfullscreen></iframe> ` ;
2023-09-24 07:15:38 -04:00
}
2023-09-24 10:04:09 -04:00
if ( invidious _embed . api _promise ) {
return new Promise ( async ( resolve , reject ) => {
2023-09-24 07:15:38 -04:00
resolve ( iframe _constractor ( await this . getTitle ( ) ) ) ;
} )
}
2023-09-24 10:04:09 -04:00
else {
2023-09-24 07:15:38 -04:00
return iframe _constractor ( this . getTitle ( ) ) ;
}
2023-08-17 22:40:50 -04:00
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* Get current playing time start with video 0 seconds
*
* Default return number
*
* But if set invidious _embed . api _promise true , return Promise < number > .
* @ returns { number | Promise < number > }
* @ 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
* /
2023-08-29 11:52:58 -04:00
getCurrentTime ( ) {
2023-08-17 22:40:50 -04:00
return this . promise _send _event ( 'getcurrenttime' ) ;
}
2023-08-29 11:52:58 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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}
* /
2023-09-02 13:17:14 -04:00
async getVideoData ( ) {
2023-09-06 10:31:09 -04:00
const videoData = await this . videodata _api ( this . videoId ) ;
2023-09-24 10:04:09 -04:00
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
} ;
2023-09-02 13:17:14 -04:00
}
2023-09-25 11:06:47 -04:00
/ * *
* Get playlist index which count start with 0
*
* Default return number
*
* But if set invidious _embed . api _promise true , return Promise < number > .
* @ returns { number | Promise < number > }
* @ 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
* /
2023-09-02 13:17:14 -04:00
getPlaylistIndex ( ) {
return this . promise _send _event ( 'getplaylistindex' ) ;
}
2023-09-25 11:06:47 -04:00
/ * *
* Get playlist videoIds
* @ returns { [ string ] | undefined }
* @ example
* const playlist _videoids = player . getPlaylist ( ) ;
* //playlist_videoids = ['i50sUufNbzY','BgNVwiX7K8E','L7PCS7afS3Y'];
* /
2023-09-02 13:17:14 -04:00
getPlaylist ( ) {
return this . playlistVideoIds !== undefined ? this . playlistVideoIds : [ ] ;
}
2023-09-25 11:06:47 -04:00
/ * *
* set loop video or not
* @ param { boolean } loopStatus
* @ example
* player . setLoop ( true ) ;
* /
2023-09-06 10:31:09 -04:00
setLoop ( loopStatus ) {
if ( typeof loopStatus === 'boolean' ) {
this . loop = loopStatus ;
} else {
console . error ( 'setLoop first argument must be bool' ) ;
}
}
2023-08-29 11:52:58 -04:00
constructor ( element , options ) {
this . Player ( element , options ) ;
window . addEventListener ( 'message' , ( ms ) => { this . receiveMessage ( ms ) } ) ;
2023-09-24 10:04:09 -04:00
this . message _wait = {
getvolume : [ ] ,
getmutestatus : [ ] ,
getduration : [ ] ,
getcurrenttime : [ ] ,
getplaybackrate : [ ] ,
getavailableplaybackrates : [ ] ,
gettitle : [ ]
} ;
2023-08-17 22:40:50 -04:00
}
}
2023-09-06 10:31:09 -04:00
2023-09-25 11:06:47 -04:00
/ * *
* 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
* /
2023-08-29 11:52:58 -04:00
function invidious _ready ( func ) {
if ( typeof func === 'function' ) {
2023-08-17 22:40:50 -04:00
func ( ) ;
}
2023-09-02 13:17:14 -04:00
else {
console . warn ( 'invidious.ready first argument must be function' ) ;
}
2023-08-17 22:40:50 -04:00
}
2023-09-06 10:31:09 -04:00
2023-09-25 11:06:47 -04:00
invidious _embed . invidious _instance = new URL ( document . currentScript . src ) . origin ; //set default instance using load origin of js file instance
2023-09-24 10:04:09 -04:00
const invidious = {
Player : invidious _embed ,
PlayerState : {
ENDED : 0 ,
PLAYING : 1 ,
PAUSED : 2 ,
BUFFERING : 3 ,
CUED : 5
} ,
ready : invidious _ready
} ;
2023-09-02 13:27:05 -04:00
if ( typeof onInvidiousIframeAPIReady === 'function' ) {
2023-09-07 11:46:37 -04:00
try {
2023-09-02 20:04:04 -04:00
onInvidiousIframeAPIReady ( ) ;
2023-09-07 11:46:37 -04:00
} catch ( e ) {
2023-09-02 20:04:04 -04:00
console . error ( e ) ;
}
2023-08-17 22:40:50 -04:00
}