@@ -18,10 +18,32 @@ function sepiaFW_build_ui_cards(){
1818
1919 //states
2020 Cards . currentCardId = 0 ; //increasing id for every card element
21- var topIndexZ = 1 ; //to get an active element to the top
21+ var topIndexZ = 1 ; //to get an active element to the top (deprecated?)
2222
2323 //some specials
24- Cards . allowWebPlayer = false ; //deactivated by default because it only works in desktop and gives no control over start/stop/volume
24+ Cards . canEmbedYouTube = true ;
25+ Cards . canEmbedSpotify = false ; //deactivated by default because it only works in desktop and gives no control over start/stop/volume
26+ Cards . canEmbedAppleMusic = false ; //"" ""
27+ Cards . canEmbedWebPlayer = function ( service ) {
28+ if ( ! service ) return false ;
29+ service = service . toLowerCase ( ) . replace ( / \s + / , "_" ) ; //support brands too
30+ if ( service . indexOf ( "spotify" ) == 0 ) {
31+ return Cards . canEmbedSpotify ;
32+ } else if ( service . indexOf ( "apple_music" ) == 0 ) {
33+ return Cards . canEmbedAppleMusic ;
34+ } else if ( service . indexOf ( "youtube" ) == 0 ) {
35+ return Cards . canEmbedYouTube ;
36+ } else {
37+ return false ;
38+ }
39+ }
40+ Cards . getSupportedWebPlayers = function ( ) {
41+ var players = [ ] ;
42+ if ( Cards . canEmbedYouTube ) players . push ( "youtube" ) ;
43+ if ( Cards . canEmbedSpotify ) players . push ( "spotify" ) ;
44+ if ( Cards . canEmbedAppleMusic ) players . push ( "apple_music" ) ;
45+ return players ;
46+ }
2547
2648 //get a full card result as DOM element
2749 Cards . get = function ( assistAnswer , sender ) {
@@ -989,7 +1011,6 @@ function sepiaFW_build_ui_cards(){
9891011 //LINK
9901012
9911013 var currentLinkItemId = 0 ;
992- var youTubeMessageListenerExists = false ;
9931014
9941015 function buildLinkElement ( cardElementInfo ) {
9951016 var newId = ( "sepiaFW-card-id-" + Cards . currentCardId ++ ) ;
@@ -1019,7 +1040,7 @@ function sepiaFW_build_ui_cards(){
10191040 } else if ( data . type == "default" ) {
10201041 //overwrite with default link icon
10211042 leftElement = "<div class='linkCardLogo'>" + "<i class='material-icons md-mnu'>link</i>" + "</div>" ; //language
1022- } else if ( data . type == "musicSearch" ) {
1043+ } else if ( data . type == "musicSearch" || data . type == "videoSearch" ) {
10231044 //we could check the brand here: data.brand, e.g. Spotify, YouTube, ...
10241045 linkCardEle . className += ( " " + data . brand ) ;
10251046 }
@@ -1037,16 +1058,16 @@ function sepiaFW_build_ui_cards(){
10371058 cardBody . appendChild ( linkCardEle ) ;
10381059
10391060 //Experimenting with web players
1040- if ( Cards . allowWebPlayer || data . embedded ) {
1061+ if ( Cards . canEmbedWebPlayer ( data . brand ) && linkUrl && data . type && ( data . type == "musicSearch" || data . type == "videoSearch" ) ) {
10411062 //Spotify
1042- if ( data . type && data . type == "musicSearch" && data . brand == "Spotify" && linkUrl ) {
1063+ if ( data . brand == "Spotify" ) {
10431064 var webPlayerDiv = document . createElement ( 'DIV' ) ;
10441065 webPlayerDiv . className = "spotifyWebPlayer cardBodyItem fullWidthItem" ;
10451066 var contentUrl = linkUrl . replace ( "spotify:" , "https://open.spotify.com/embed/" ) . replace ( ":play" , "" ) . replace ( / : / g, "/" ) . trim ( ) ;
10461067 webPlayerDiv . innerHTML = '<iframe src="' + contentUrl + '" width="100%" height="80" frameborder="0" allowtransparency="true" allow="encrypted-media"></iframe>' ;
10471068 cardBody . appendChild ( webPlayerDiv ) ;
10481069 //Apple Music
1049- } else if ( data . type && data . type == "musicSearch" && data . brand == "Apple Music" && linkUrl ) {
1070+ } else if ( data . brand == "Apple Music" ) {
10501071 var webPlayerDiv = document . createElement ( 'DIV' ) ;
10511072 webPlayerDiv . className = "appleMusicWebPlayer cardBodyItem fullWidthItem" ;
10521073 webPlayerDiv . innerHTML = '<iframe '
@@ -1059,7 +1080,7 @@ function sepiaFW_build_ui_cards(){
10591080 + '</iframe>' ;
10601081 cardBody . appendChild ( webPlayerDiv ) ;
10611082 //YouTube - TODO: improve server-side to give brand and correct URL and return data.embedded
1062- } else if ( linkUrl . indexOf ( 'youtube' ) ) {
1083+ } else if ( data . brand == "YouTube" ) {
10631084 var webPlayerDiv = document . createElement ( 'DIV' ) ;
10641085 var playerId = currentLinkItemId ++ ;
10651086 webPlayerDiv . className = "youTubeWebPlayer cardBodyItem fullWidthItem"
@@ -1069,32 +1090,15 @@ function sepiaFW_build_ui_cards(){
10691090 f . frameBorder = 0 ;
10701091 f . style . width = "100%" ; f . style . height = "280px" ; f . style . overflow = "hidden" ;
10711092 f . style . border = "4px solid" ; f . style . borderColor = "#212121" ;
1072- f . onload = function ( ) {
1073- //API
1074- if ( ! youTubeMessageListenerExists ) {
1075- youTubeMessageListenerExists = true ;
1076- window . addEventListener ( 'message' , function ( e ) {
1077- if ( e . origin == "https://www.youtube.com" && e . data && typeof e . data == "string" && e . data . indexOf ( "{" ) == 0 ) {
1078- var data = JSON . parse ( e . data ) ;
1079- if ( data && data . id ) {
1080- //f.contentWindow.postMessage(JSON.stringify({event:'command', func:'stopVideo'}), "*"); //playVideo, paus.., stop.., next..,
1081- if ( data . event == 'onReady' ) {
1082- $ ( '#' + data . id ) [ 0 ] . contentWindow . postMessage ( JSON . stringify ( { event :'command' , func :'playVideo' } ) , "*" ) ;
1083- } else if ( data . event == 'infoDelivery' && data . info && data . info . playerState == - 1 ) {
1084- //console.log(JSON.stringify(data));
1085- //TODO: sometimes fails AND should not end up in an endless loop!
1086- if ( data . info . availableQualityLevels . length == 0 ) {
1087- $ ( '#' + data . id ) [ 0 ] . contentWindow . postMessage ( JSON . stringify ( { event :'command' , func :'nextVideo' } ) , "*" ) ;
1088- }
1089- }
1090- }
1091- }
1092- } ) ;
1093- }
1094- f . contentWindow . postMessage ( JSON . stringify ( { event :'listening' , id : f . id } ) , "*" ) ;
1095- } ;
1096- //https://www.youtube.com/results?search_query=purple+haze%2C+jimi+hendrix
1097- f . src = "https://www.youtube.com/embed?autoplay=1&enablejsapi=1&listType=search&list=" + linkUrl . replace ( / .* ?s e a r c h _ q u e r y = / , "" ) . trim ( ) ;
1093+ if ( data . autoplay ) {
1094+ addYouTubeControls ( f ) ;
1095+ }
1096+ if ( linkUrl . indexOf ( "/embed" ) < 0 ) {
1097+ //convert e.g.: https://www.youtube.com/results?search_query=purple+haze%2C+jimi+hendrix
1098+ f . src = "https://www.youtube.com/embed?autoplay=1&enablejsapi=1&playsinline=1&fs=1&listType=search&list=" + linkUrl . replace ( / .* ?s e a r c h _ q u e r y = / , "" ) . trim ( ) ;
1099+ } else {
1100+ f . src = linkUrl ;
1101+ }
10981102 cardBody . appendChild ( webPlayerDiv ) ;
10991103 webPlayerDiv . appendChild ( f ) ;
11001104 }
@@ -1625,6 +1629,91 @@ function sepiaFW_build_ui_cards(){
16251629 }
16261630 return footer ;
16271631 }
1632+
1633+ //--- YouTube Card Controls ---
1634+
1635+ var youTubeMessageListenerExists = false ;
1636+ var youTubePlayersTriedToStart = { } ;
1637+ var youTubeLastActivePlayerId = undefined ;
1638+ var youTubeLastActivePlayerState = undefined ;
1639+
1640+ function addYouTubeControls ( frameEle ) {
1641+ frameEle . onload = function ( ) {
1642+ //API
1643+ if ( ! youTubeMessageListenerExists ) {
1644+ youTubeMessageListenerExists = true ;
1645+ window . addEventListener ( 'message' , function ( e ) {
1646+ if ( e . origin == "https://www.youtube.com" && e . data && typeof e . data == "string" && e . data . indexOf ( "{" ) == 0 ) {
1647+ var data = JSON . parse ( e . data ) ;
1648+ if ( data && data . id ) {
1649+ var $player = $ ( '#' + data . id ) ;
1650+ //console.log("YouTube iframe event: " + data.event);
1651+ if ( $player . length == 0 ) {
1652+ return ;
1653+ }
1654+ if ( data . event == 'onReady' ) {
1655+ $player [ 0 ] . contentWindow . postMessage ( JSON . stringify ( { event :'command' , func :'playVideo' } ) , "*" ) ;
1656+ } else if ( data . event == 'infoDelivery' && data . info ) {
1657+ //console.log(JSON.stringify(data));
1658+ if ( data . info . playerState != undefined ) youTubeLastActivePlayerState = data . info . playerState ;
1659+ if ( data . info . playerState == - 1 ) {
1660+ //Skip if faulty
1661+ if ( data . info . availableQualityLevels . length == 0 || data . info . videoBytesTotal == 1 ) {
1662+ //console.log(data.info.playlist.length - 1);
1663+ //console.log(data.info.playlistIndex);
1664+ if ( ! youTubePlayersTriedToStart [ data . id ] && data . info . playlistIndex == 0 ) {
1665+ youTubePlayersTriedToStart [ data . id ] = true ;
1666+ $player [ 0 ] . contentWindow . postMessage ( JSON . stringify ( { event :'command' , func :'nextVideo' } ) , "*" ) ;
1667+ } else if ( data . info . playlist && data . info . playlist . length > 0 ) {
1668+ if ( data . info . playlistIndex != undefined && data . info . playlistIndex < ( data . info . playlist . length - 1 ) ) {
1669+ //console.log('--- next ---');
1670+ $player [ 0 ] . contentWindow . postMessage ( JSON . stringify ( { event :'command' , func :'nextVideo' } ) , "*" ) ;
1671+ delete youTubePlayersTriedToStart [ data . id ] ;
1672+ }
1673+ }
1674+ }
1675+ } else {
1676+ youTubeLastActivePlayerId = data . id ;
1677+ }
1678+ }
1679+ }
1680+ }
1681+ } ) ;
1682+ }
1683+ frameEle . contentWindow . postMessage ( JSON . stringify ( { event :'listening' , id : frameEle . id } ) , "*" ) ;
1684+ } ;
1685+ }
1686+ Cards . youTubePlayerGetState = function ( ) {
1687+ if ( youTubeLastActivePlayerId ) {
1688+ var $player = $ ( '#' + youTubeLastActivePlayerId ) ;
1689+ if ( $player . length == 0 ) {
1690+ return 0 ;
1691+ } else {
1692+ return youTubeLastActivePlayerState ;
1693+ }
1694+ } else {
1695+ return 0 ;
1696+ }
1697+ }
1698+ Cards . youTubePlayerControls = function ( cmd ) {
1699+ if ( youTubeLastActivePlayerId && youTubeLastActivePlayerState > 0 ) {
1700+ var $player = $ ( '#' + youTubeLastActivePlayerId ) ;
1701+ if ( $player . length == 0 ) {
1702+ return 0 ;
1703+ } else {
1704+ if ( cmd == "stop" || cmd == "pause" ) {
1705+ $player [ 0 ] . contentWindow . postMessage ( JSON . stringify ( { event :'command' , func :'pauseVideo' } ) , "*" ) ; //NOTE: we use pause for now because stop triggers next video
1706+ return 1 ;
1707+ } else if ( cmd == "next" ) {
1708+ $player [ 0 ] . contentWindow . postMessage ( JSON . stringify ( { event :'command' , func :'nextVideo' } ) , "*" ) ;
1709+ return 1 ;
1710+ } else {
1711+ return 0 ;
1712+ }
1713+ //frameEle.contentWindow.postMessage(JSON.stringify({event:'command', func:'stopVideo'}), "*"); //playVideo, paus.., stop.., next..,
1714+ }
1715+ }
1716+ }
16281717
16291718 return Cards ;
16301719}
0 commit comments