0

Working on a Chrome extension, injecting script to Youtube site itself, and listening to the player events using the Youtube player API.

Injected script code:
(you can run it in the console on a Youtube HTML5 video page and see that it works)

var theYTplayer = document.getElementById('movie_player');

theYTplayer.addEventListener('onStateChange', function(){
    console.log('--- player state has been changed');
});

When it comes to HTML5 videos, it all works great, but not in flash live streams.
In Flash, for some reason all the GET functions returns errors, while the SET functions works good.
GET: getPlayerState, isMuted, getVolume
SET: playVideo, pauseVideo, mute, unMute, setVolume

This may be a bug, so I reported about it here: issue #7043

You can test it on this live streaming video for example.

If I'm doing something wrong then please let me know how it should be done.
In case and it is a bug, then lets think of another way to detect whether the video is playing or not.
I compared between the two DOMs and there is no any class being added when the state changes or anything else, the files ended to be match.
Then I saw that when the video is playing Chrome adds the new extra favicon speaker to the tab, but that's only if there is sound, if the video is muted then the icon will not appear so this is no good. so do you may have any other ideas/workarounds how to detect if the video is playing or not please or fixing my code?

Gil Goldshlager
  • 651
  • 1
  • 8
  • 22
  • Make sure this is not your problem: http://stackoverflow.com/questions/9515704/building-a-chrome-extension-inject-code-in-a-page-using-a-content-script – Xan Mar 29 '15 at 12:35
  • @Xan: thanks but this isn't my problem, I do inject the script to the page and doing that after the DOM has been loaded, go try to play with it in the console in a HTML5 video and in a flash video on Youtube and you will see the issue I'm facing. – Gil Goldshlager Mar 29 '15 at 12:43
  • I suspected as much, I just wanted to clarify you do inject the script properly. – Xan Mar 29 '15 at 12:43

2 Answers2

1

EDIT!
This is a workaround which works but it isn't the correct answer, the one you are looking for is this: https://stackoverflow.com/a/29346099/962643


A workaround,
It takes some time for the flash player to finish loading and to it's element (#movie_player) to become an object.
Using setInterval to check whether the element is null or not, and only when it isn't null then it means we can use it with the GET functions, in this case it will be using the getPlayerState function, using it in another setInterval to check the state will give you a fake "onStateChange" listener, as unfortunately adding an event listener still doesn't work even when the player has been fully loaded, but the GET functions do works so we will use them instead.

In my case I only need to know the state when I open the pageAction popup in my extension, so just calling the getPlayerState function when the popup loads it's good enough for me.

In the injected script:

var theYTplayer = document.getElementById('movie_player'),
    theYTplayerState;

var checkYTplayerReady = setInterval(function(){
    theYTplayer = document.getElementById('movie_player');

    if(theYTplayer != null){
        clearInterval(checkYTplayerReady);

        setTimeout(function(){
            checkYTplayerState();
        },2000);

        theYTplayer.addEventListener('onStateChange', checkYTplayerState);
    }
}, 100);

function checkYTplayerState(event){ 
    theYTplayerState = event;

    if(typeof event === 'undefined'){
        theYTplayerState = theYTplayer.getPlayerState();
    }

    if(theYTplayerState == 1){
        theYTplayerState = 'playing';
    }else{
        theYTplayerState = 'not playing (unstarted/paused/buffering/ended/video cued)';
    }

    return theYTplayerState;
}

From there you can use messages to send it to the content script and then to the popup or use it to whatever you need to.

Community
  • 1
  • 1
Gil Goldshlager
  • 651
  • 1
  • 8
  • 22
  • \*cough\* [`MutationObserver`](http://updates.html5rocks.com/2012/02/Detect-DOM-changes-with-Mutation-Observers) \*cough\* – Xan Mar 29 '15 at 20:41
  • @Xan: haha yes using `MutationObserver` is great (and you too and for commenting so fast! thanks) and I'm using it for other things in this extension, but how do you use it for checking an element's type if it's an object? or did you mean to use it in another way please? – Gil Goldshlager Mar 29 '15 at 21:00
  • Wait. I'm surprised your code works at all. If you do not try to assign `theYTplayer` again, how can it become non-`null`? – Xan Mar 29 '15 at 21:02
  • Right thanks for that! I forgot to add it here, edited my code. now how the `MutationObserver` magic goes please? if and – Gil Goldshlager Mar 29 '15 at 21:17
  • You just need to observe all insertions until the element appears within one. Using [`mutation-summary` library](https://code.google.com/p/mutation-summary/) should be dead simple for that (it saves you the work iterating through inserted subtrees) – Xan Mar 29 '15 at 22:21
  • Interesting, will give it a try, in this case the flash is already in the DOM, so no subtrees changes, it only takes time until the flash player finish to set itself and be ready to play the video or accepts calls. anyway I have just found the real issue I had in my code from the beginning so going to post another answer now. but this method does still work in case and there will be such a bug with the API. – Gil Goldshlager Mar 30 '15 at 11:25
0

I have found the issue in my code and how to use the on state change listener.

While in Javascript when you use addEventListener you can set the callback function name without quotes like so:

player.addEventListener('onStateChange', playerStateChange);

BUT!
when it comes to the Youtube player API, you must set the function's name as a string, which means with quotes:

player.addEventListener('onStateChange', 'playerStateChange');

so...yep that was the whole issue, while it works with the HTML5 player without quotes, it doesn't when it comes to the flash player.

From the documentations:

Adding or removing an event listener

player.addEventListener(event:String, listener:String):Void

Adds a listener function for the specified event. The Events section below identifies the different events that the player might fire. The listener is a string that specifies the function that will execute when the specified event fires.

https://developers.google.com/youtube/iframe_api_reference#Adding_event_listener

The only place where you can see it clearly in the docs it is in the JS API under the "Subscribing to Events" section.

Subscribe to events
by adding an event listener to the player reference. For example, to get notified when the player's state changes, add an event listener for onStateChange and include a callback function.

function onYouTubePlayerReady(playerId) {
    ytplayer = document.getElementById("myytplayer");  
    ytplayer.addEventListener("onStateChange", "onytplayerStateChange");
}

function onytplayerStateChange(newState) {
    alert("Player's new state: " + newState);
}

https://developers.google.com/youtube/js_api_reference#SubscribingEvents

Now after doing so you can add the listener to the player after it is ready and it will work just fine.
To know when the player is ready, you need to inject your script at document_start using run_at in the manifest file. Then in your code use the function name onYouTubePlayerReady like so:

var theYTplayer;

function onYouTubePlayerReady(){
    theYTplayer = document.getElementById('movie_player');
    theYTplayer.addEventListener('onStateChange', 'playerStateChange');
}

Do not use the iframe API function name onYouTubeIframeAPIReady as it won't work, only the JS API function name onYouTubePlayerReady .

While the previous method I have posted works, this is the right one.

Gil Goldshlager
  • 651
  • 1
  • 8
  • 22