0

I'm having issues with the new CAF receiver not registering the YouTube iframe player as a player, and that it is playing. After 5 minutes connected to the receiver, the connection is broken because it thinks that the player is idle.

This is the sender-code

var metadata = new chrome.cast.media.GenericMediaMetadata();
metadata.title = "Foo - Bar";
metadata.image = 'https://img.youtube.com/vi/IXNrHusLXoM/mqdefault.jpg';
metadata.images = ['https://img.youtube.com/vi/IXNrHusLXoM/mqdefault.jpg'];
var mediaInfo = new chrome.cast.media.MediaInfo();
mediaInfo.contentType = "video/*";
mediaInfo.contentId ="IXNrHusLXoM";
mediaInfo.duration = 300;
var request = new chrome.cast.media.LoadRequest();
request.media = mediaInfo;
request.customData = customData;
request.metadata = metadata;
castSession.loadMedia(request).then(
  function() {
      console.log('Load succeed');
  },
  function(errorCode) {
      console.log('Error code: ' + errorCode);
});

The receiver code can be found here: https://github.com/zoff-music/zoff-chromecast-receiver/blob/feature/v3/receiver.js

Is there any way of having the new CAF receiver hook into the YouTube iframe player, or "manually" dispatch LOADED, BUFFERING, PLAYING, PAUSED, STOPPED events so that the receiver doesn't disconnect from the sender?

Edit: with the above code, the PlayerState gets to the BUFFERING stage, but stops there. The promise with the log "Load succeed" is never triggered.

2 Answers2

1

I managed to trick the receiver with fake mediaEelement. You can see the code in pastebin

const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();

var yt_events = {};
var pause_request = false;
var yt_player;

var yt_video_fake = {
    removeAttribute: function(attr) {
    },
    setAttribute: function(attr, val) {
    },

    getCurrentTimeSec: function() { return yt_player && yt_player.getCurrentTime ? yt_player.getCurrentTime() : 0; },
    getDurationSec: function() { return yt_player ? yt_player.getDuration() : 0; },
    getVolume: function() {
        if(!yt_player || !yt_player.getVolume) {
            return 0;
        }

        var volume = new cast.framework.messages.Volume();

        volume.level = yt_player.getVolume() / 100;
        volume.muted = yt_player.isMuted() ? true : false;

        return volume;
    },
    setVolume: function(vol) { yt_player && yt_player.setVolume(vol.level * 100); },
    getState: function() {
        if(!yt_player || !yt_player.getPlayerState) {
            return 'IDLE';
        }

        var state = yt_player.getPlayerState();
        var _state;

        if(pause_request) {
            pause_request = false;
            state = YT.PlayerState.PAUSED;
        }

        switch(state) {
            default: case YT.PlayerState.UNSTARTED:
                _state = 'IDLE';
            break;

            case YT.PlayerState.PLAYING:
                _state = 'PLAYING';
            break;

            case YT.PlayerState.PAUSED:
                _state = 'PAUSED';
            break;

            case YT.PlayerState.BUFFERING:
                _state = 'BUFFERING';
            break;

            case YT.PlayerState.ENDED:
                _state = 'ENDED';
            break;
        }

        return _state;
    },

    addEventListener: function(e, func) { },
    load: function() {},
    play: function() { yt_player && yt_player.playVideo(); },
    pause: function() { if(yt_player && yt_player.pauseVideo) {pause_request = true; yt_player.pauseVideo(); }},
    seek: function(timeTo) { yt_player && yt_player.seekTo(timeTo, true);},
    reset: function() {
        if(yt_player) {
            try { yt_player.destroy && yt_player.destroy(); } catch(e) {
                //console.trace(e);
            };
            delete yt_player;
        }
    },

    registerErrorCallback: function(func) { yt_events['error'] = func; },
    registerEndedCallback: function(func) { yt_events['ended'] = func; },
    registerLoadCallback: function(func) { yt_events['load'] = func; },

    unregisterErrorCallback: function () { delete yt_events['error'] },
    unregisterEndedCallback: function () { delete yt_events['ended']},
    unregisterLoadCallback: function () { delete yt_events['load']}
};

Object.defineProperty(yt_video_fake, 'currentTime', {
    val1: null,
    get: function() { return yt_player && yt_player.getCurrentTime ? yt_player.getCurrentTime() : this.val1; },
    set: function(newValue) {
        yt_player && yt_player.seekTo(newValue, true);
        this.val1 = newValue;
    },
    enumerable      : true,
    configurable    : true
});

Object.defineProperty(yt_video_fake, 'volume', {
    val1: null,
    get: function() { var vol = this.getVolume(); if(vol) return vol['level']; return 1; },
    set: function(newValue) {
        yt_player && yt_player.setVolume && yt_player.setVolume(newValue * 100);
        this.val1 = newValue;
    },
    enumerable      : true,
    configurable    : true
});


Object.defineProperty(yt_video_fake, 'duration', {
    val1: null,
    get: function() { return this.getDurationSec(); },
    set: function() {},
    enumerable      : true,
    configurable    : true
});


function YoutubePlayMedia(videoid) {
    var yt_container = $('#yt_container');

    if(!yt_container.length) {
        yt_container = $('<div id="yt_container" style="position:absolute;top:0;left:0;width:100%;height:100%;"></div>');
        $('body').append(yt_container);
    }

    yt_container.html('<iframe id="youtube_container" style="width:100%;height:100%;" frameborder="0" allowfullscreen="1" allow="autoplay; encrypted-media" title="YouTube video player" src="//www.youtube.com/embed/' + videoid +'?autoplay=1&enablejsapi=1&modestbranding=1&controls=0&fs=0&iv_load_policy=3&rel=0&cc_load_policy=1&cc_lang_pref=bg"></iframe>');

    yt_player = new YT.Player('youtube_container', {
        events: {
            'onReady': function(e) {
                yt_player.is_loaded = true;
                yt_player.playVideo();
            },
            'onStateChange': function(e) {
                switch(e.data) {
                    case YT.PlayerState.PLAYING:
                        if(yt_player.is_loaded) {
                            if(yt_events['load']) {
                                yt_events['load']();
                            }
                        }

                    break;

                    case YT.PlayerState.ENDED:
                        //yt_events['ended'] && yt_events['ended'](e);
                    break;
                }
            },
            'onError': function(e) {
                //yt_events['error'] && yt_events['error'](e);
            }
        }
    });
}

function YoutubeLoadMedia(url) {
    current_media_type = 'Youtube';

    window.onYouTubeIframeAPIReady = function() {
        window.youtube_loaded = true;
        YoutubePlayMedia(url);
    }

    if(!window.youtube_script) {
        window.youtube_script = document.createElement('script');
        window.youtube_script.src = "https://www.youtube.com/iframe_api";
        var firstScriptTag = document.getElementsByTagName('script')[0];
        firstScriptTag.parentNode.insertBefore(window.youtube_script, firstScriptTag);
    } else {
        // Вече имаме api направо действаме
        YoutubePlayMedia(url);
    }
}


playerManager.setMessageInterceptor(
    cast.framework.messages.MessageType.LOAD,
    loadRequestData => {
        if (loadRequestData.media && loadRequestData.media.contentId) {
            YoutubeLoadMedia(loadRequestData.media.contentId);
            playerManager.setMediaElement(yt_video_fake);

            return false;
        }

        return loadRequestData;
    }
);

const options = new cast.framework.CastReceiverOptions();
options.disableIdleTimeout = true;

context.start(options);
  • What is the `YT` object/class? I dont see that defined anywhere? For example, its used here `case YT.PlayerState.UNSTARTED` – alexward1230 Dec 25 '21 at 07:16
  • YT is defined from youtube iframe api script that i inject https://developers.google.com/youtube/iframe_api_reference – Jordan Vichev Dec 26 '21 at 09:01
0

The YouTube iframe player is not designed to be a player for a Cast receiver.

Leon Nicholls
  • 4,623
  • 2
  • 16
  • 17
  • Is there a way of hooking into the state of the Cast receiver, to allow more custom media, opening for an actual custom cast receiver with the new CAF receiver framework? – Kasper Rynning-Tønnesen Mar 27 '18 at 12:52