最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Youtube player.destroy(); throws 'this.a is null', even when validating player - Stack Overflow

programmeradmin1浏览0评论

So I've got a small app with two panels. Using the iframe API. Clicking on one panel will expand the panel full screen, as well as showing a 'play video' button with some additional information. Clicking a button in the top left will return the UI to it's standard state, closing down the video and shrinking the panels back to fit 50/50.

Now as we've got two videos, I've defined the videos as such, #vidPlayer2 being the second trigger.

$('#vidPlayer1').on('click', function(){
  player = new YT.Player('player', {
    height: '100%',
    width: '100%',
    videoId: '(video id here)',
    controls: 0,
    showinfo: 0,
    autoplay: 0,
    rel: 0,
    events: {
      'onReady': onPlayerReady,
      'onStateChange': onPlayerStateChange
    }
  });

Similarly, we've got the default demo code, with a small modification:

function onPlayerReady(event) {
  event.target.playVideo();
}

var done = false;
function onPlayerStateChange(event) {
  if (event.data == YT.PlayerState.PLAYING && !done) {
    done = true;
  }
  if (event.data == YT.PlayerState.ENDED) {
    resetView();
  }
}
function stopVideo() {
  player.stopVideo();
}

Then, we're trying to get the button to work. In some circumstances, not having clicked one of the vidPlayer buttons, no player is defined, so I threw in an if statement with some validation.

var resetView = function() {
  // If a Youtube player is active, make sure we stop it.
  if (player === undefined || !player || null) {
    console.log("Player could not be found.");
  } else {
    player.stopVideo();
    player.destroy();
  }
  // Additional code to reset the UI removed below. Works no matter what if the above code is removed.
};

Now for the most part, things work well UNTIL I try to go into a panel, play a video, reset UI, then try to enter and leave the next panel without playing a video. When I follow this exact series of steps, regardless of what panel starts first, I get a TypeError: this.a is null in console. I would've assumed that the validation would've done the trick, but apparently not.

So what I can distinguish from this is it works fine when initialized - i.e. var player is initialized. The return button works through just going back and forth without playing a video. The return button works when a video is actively playing, but the function fails if we try to use the return directly after the player is stopped and destroyed. It does work if we simply pop open another video, however.

Is there something I'm missing when I'm trying to reset the view? Does the youtube player have to be reinitialized? Any help or thoughts would be greatly appreciated. Thanks!

Edit: This is the note that's being thrown by the console. Something to note is main.js:44:5 is the player.stopVideo(); call, and main.js:70:3 is when resetView(); is called on a button click.

TypeError: this.a is null
www-widgetapi.js:120:73
f.C
.js:120:73
V
.js:112:97
Nb/</this[a]
.js:130:124
resetView
file:///Users/cipher/Desktop/ERHS_video/js/main.js:44:5
<anonymous>
file:///Users/cipher/Desktop/ERHS_video/js/main.js:70:3
dispatch
file:///Users/cipher/Desktop/ERHS_video/js/jquery-3.2.1.min.js:3:10264
add/q.handle
file:///Users/cipher/Desktop/ERHS_video/js/jquery-3.2.1.min.js:3:8326

So I've got a small app with two panels. Using the iframe API. Clicking on one panel will expand the panel full screen, as well as showing a 'play video' button with some additional information. Clicking a button in the top left will return the UI to it's standard state, closing down the video and shrinking the panels back to fit 50/50.

Now as we've got two videos, I've defined the videos as such, #vidPlayer2 being the second trigger.

$('#vidPlayer1').on('click', function(){
  player = new YT.Player('player', {
    height: '100%',
    width: '100%',
    videoId: '(video id here)',
    controls: 0,
    showinfo: 0,
    autoplay: 0,
    rel: 0,
    events: {
      'onReady': onPlayerReady,
      'onStateChange': onPlayerStateChange
    }
  });

Similarly, we've got the default demo code, with a small modification:

function onPlayerReady(event) {
  event.target.playVideo();
}

var done = false;
function onPlayerStateChange(event) {
  if (event.data == YT.PlayerState.PLAYING && !done) {
    done = true;
  }
  if (event.data == YT.PlayerState.ENDED) {
    resetView();
  }
}
function stopVideo() {
  player.stopVideo();
}

Then, we're trying to get the button to work. In some circumstances, not having clicked one of the vidPlayer buttons, no player is defined, so I threw in an if statement with some validation.

var resetView = function() {
  // If a Youtube player is active, make sure we stop it.
  if (player === undefined || !player || null) {
    console.log("Player could not be found.");
  } else {
    player.stopVideo();
    player.destroy();
  }
  // Additional code to reset the UI removed below. Works no matter what if the above code is removed.
};

Now for the most part, things work well UNTIL I try to go into a panel, play a video, reset UI, then try to enter and leave the next panel without playing a video. When I follow this exact series of steps, regardless of what panel starts first, I get a TypeError: this.a is null in console. I would've assumed that the validation would've done the trick, but apparently not.

So what I can distinguish from this is it works fine when initialized - i.e. var player is initialized. The return button works through just going back and forth without playing a video. The return button works when a video is actively playing, but the function fails if we try to use the return directly after the player is stopped and destroyed. It does work if we simply pop open another video, however.

Is there something I'm missing when I'm trying to reset the view? Does the youtube player have to be reinitialized? Any help or thoughts would be greatly appreciated. Thanks!

Edit: This is the note that's being thrown by the console. Something to note is main.js:44:5 is the player.stopVideo(); call, and main.js:70:3 is when resetView(); is called on a button click.

TypeError: this.a is null
www-widgetapi.js:120:73
f.C
https://s.ytimg./yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:120:73
V
https://s.ytimg./yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:112:97
Nb/</this[a]
https://s.ytimg./yts/jsbin/www-widgetapi-vflWgX7t4/www-widgetapi.js:130:124
resetView
file:///Users/cipher/Desktop/ERHS_video/js/main.js:44:5
<anonymous>
file:///Users/cipher/Desktop/ERHS_video/js/main.js:70:3
dispatch
file:///Users/cipher/Desktop/ERHS_video/js/jquery-3.2.1.min.js:3:10264
add/q.handle
file:///Users/cipher/Desktop/ERHS_video/js/jquery-3.2.1.min.js:3:8326
Share Improve this question edited Sep 6, 2017 at 23:41 IkeDoud asked Sep 6, 2017 at 23:14 IkeDoudIkeDoud 872 silver badges14 bronze badges 2
  • What is the stack trace of the exception? Nothing in your code provided seems to be the origin of the exception. – PMV Commented Sep 6, 2017 at 23:37
  • Apologies, am learning JS as I go, may have misunderstood what stack traces are but I've added it to the question. Something to note is main.js:44:5 is the player.stopVideo(); call, and main.js:70:3 is when resetView(); is called on a button click. – IkeDoud Commented Sep 6, 2017 at 23:42
Add a ment  | 

2 Answers 2

Reset to default 5

The problem here is player is NOT undefined. What's happening is you have a global player reference, and you're doing the following with it:

  1. Creating a player in the first panel
  2. Destroying it when the first panel closes
  3. Calling player.stopVideo() on the already destroyed player (from the first panel) when the second panel closes

Currently, player holds a reference to whatever the last YouTube player you were using is, even if that player has already been destroyed.

What you should be doing is clearing out your reference to the player when you destroy it. Destroy won't (and can't) do that. You can also simplify your if condition since !player will check for null and undefined on its own:

var resetView = function() {
  // If a Youtube player is active, make sure we stop it.
  if (!player) {
    console.log("Player could not be found.");
  } else {
    player.stopVideo();
    player.destroy();
    player = null;  // Clear out the reference to the destroyed player
  }

Highly inspired from IkeDoud's above answer and some others like this one, one year later, here is my way to avoid other video suggestions on play end in embedded API player:

<div id="player"></div>

<style>
#player{
  min-width:auto;
  min-height:auto;
}
</style>
<script>
// Load the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube./iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

// Initialise the player with options
var player;
var playerOptions = {
  videoId: 'ViDeOiD_HeRe',
  events: {
    'onStateChange': onPlayerStateChange
  }
};

// Load the video whern player ready
function onYouTubeIframeAPIReady() {
  player = new YT.Player('player', playerOptions);
}

// On video end, reset the player back to poster image
function onPlayerStateChange(event) {
  if(event.data === 0) {
    $(PaReNt_sElEcToR).find("#player").replaceWith('<div id="player"></div>');
    player = null;
    player = new YT.Player('player', playerOptions);
  }
}

</script>

No more irrelevant suggestions playable from the embedded video container.
And no autoplay (mobile forbids it anyway) to have the poster (thumbnail) image.

On event.data === 0, which is video end... Destroy and reload the Iframe.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论