javascript 使用 youtube api 加载多个视频播放器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17012886/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 06:46:44  来源:igfitidea点击:

loading multiple video players with youtube api

javascriptyoutubeyoutube-api

提问by Hello World

I need to load more than one video with youtube's API. This is my first time using it so I'm not sure what I'm doing wrong, but this is what I'm trying:

我需要使用 youtube 的 API 加载多个视频。这是我第一次使用它,所以我不确定我做错了什么,但这就是我正在尝试的:

  var player;
  var player2;
  function onYouTubePlayerAPIReady() {
    player = new YT.Player('player', {
      videoId: 'hdy78ehsjdi'
    });
    player2 = new YT.Player('player', {
      videoId: '81hdjskilct'
    });
  }

回答by Vadim Gremyachev

Since onYouTubeIframeAPIReadyfunction is supposed to called only once the following approach could be used:

由于onYouTubeIframeAPIReady函数应该只调用一次,因此可以使用以下方法:

  • initialize and save video player information (ControlId,width,height,VideoId) in array

  • call onYouTubeIframeAPIReadyfunction to create all the video players

  • 将视频播放器信息(ControlId,width,height,VideoId)初始化并保存在数组中

  • 调用onYouTubeIframeAPIReady函数来创建所有的视频播放器

Example

例子

var playerInfoList = [{id:'player',height:'390',width:'640',videoId:'M7lc1UVf-VE'},{id:'player1',height:'390',width:'640',videoId:'M7lc1UVf-VE'}];

      function onYouTubeIframeAPIReady() {
        if(typeof playerInfoList === 'undefined')
           return; 

        for(var i = 0; i < playerInfoList.length;i++) {
          var curplayer = createPlayer(playerInfoList[i]);
        }   
      }
      function createPlayer(playerInfo) {
          return new YT.Player(playerInfo.id, {
             height: playerInfo.height,
             width: playerInfo.width,
             videoId: playerInfo.videoId
          });
      }

回答by Egari

The first parameter of new YT.Player needs to be the id of the HTML element (f.e. a DIV) to be replaced with an iframe to the video. As you use 'player' for both of these objects, you will load both into the same element.

new YT.Player 的第一个参数需要是要替换为视频的 iframe 的 HTML 元素的 id (fe a DIV)。当您对这两个对象使用 'player' 时,您会将它们加载到同一个元素中。

<div id="ytplayer1"></div>
<div id="ytplayer2"></div>

<script>
  var player;
  var player2;
  function onYouTubePlayerAPIReady() {
    player = new YT.Player('ytplayer1', {
      height: '390',
      width: '640',
      videoId: 'hdy78ehsjdi'
    });
    player2 = new YT.Player('ytplayer2', {
      height: '390',
      width: '640',
      videoId: '81hdjskilct'
    });
  }
</script>

Parameters of the functions are described in the Youtube API documentation:
https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player(EDIT: changed to the right link)

Youtube API 文档中描述了这些函数的参数:
https: //developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player(编辑:更改为正确的链接)

回答by Carnix

I had a more expansive issue that boiled down to this same problem. The requirements I had were to write a JS class to manage one or more (the number can vary from 1 to infinity) video embeds. The backend system is ExpressionEngine (but that's irrelevant here). The primary goal was to set up a framework for analytics that pushes individual data to our Adobe Analytics platform. Shown here is merely the part that gives play count, it can be expanded a lot from here.

我有一个更广泛的问题,归结为同样的问题。我的要求是编写一个 JS 类来管理一个或多个(数量可以从 1 到无穷大不等)视频嵌入。后端系统是 ExpressionEngine(但这在这里无关紧要)。主要目标是建立一个分析框架,将个人数据推送到我们的 Adob​​e Analytics 平台。这里展示的只是提供播放次数的部分,它可以从这里扩展很多。

The CMS allows editors to create modules on the page that present a video. One video per module. Each module is basically a section of HTML arranged via Bootstrap 3 (irrelevant for this answer).

CMS 允许编辑人员在呈现视频的页面上创建模块。每个模块一个视频。每个模块基本上都是通过 Bootstrap 3 排列的 HTML 部分(与此答案无关)。

The relevant HTML looks like this:

相关的 HTML 如下所示:

<div id="js_youTubeContainer_{innov_mod_ytplayer:id}" class="embed-responsive embed-responsive-16by9">
  <div id="js_youTubeFrame_{innov_mod_ytplayer:id}" class="embed-responsive-item"></div>
</div>

The part that says "{innov_mod_ytplayer:id}" is the YouTube Video ID from our CMS. This allows for a unique ID for each embeded item. This is important later.

显示“{innov_mod_ytplayer:id}”的部分是来自我们 CMS 的 YouTube 视频 ID。这允许为每个嵌入的项目提供唯一的 ID。这在以后很重要。

Below this, I then render out:

在此之下,我然后呈现:

            var innovYouTube_{innov_mod_ytplayer:id} = new Ariba.Innovations.YouTube.Class({
                'innovYouTubeVideoId': '{innov_mod_ytplayer:id}',
                'innovYouTubeVideoTitle': '{innov_mod_ytplayer:title}',
                'innovYouTubeDivId' : 'js_youTubeFrame_{innov_mod_ytplayer:id}'
            });
            innovYouTube_{innov_mod_ytplayer:id}.Init(); // And... Go!

            var onYouTubeIframeAPIReady = (function() {
                try{ //wrap this in try/catch because it actually throws errors when it runs subsequent times - this is expected as it's related to YouTube "rerunning" the function on other videos.
                    innovYouTube_{innov_mod_ytplayer:id}.config.functionCache = onYouTubeIframeAPIReady; //cache the existing global function
                    return function() {
                        try{
                            innovYouTube_{innov_mod_ytplayer:id}.onYouTubeIframeAPIReady(); //execute this instance's function
                            var newOnYouTubeIframeAPIReady = innovYouTube_{innov_mod_ytplayer:id}.config.functionCache.apply(this, arguments); //add instances to global function
                            return newOnYouTubeIframeAPIReady; //update global function
                        }catch(err){}
                    };
                }catch(err){}
            })();

You'll see some ExpressionEngine template tags here too - those are just the Video ID and the Video Title from YouTube. To replicate this, you'll need to change those of course.

您还会在此处看到一些 ExpressionEngine 模板标签 - 这些只是来自 YouTube 的视频 ID 和视频标题。要复制这一点,您当然需要更改这些内容。

What this does is allow me to dynamically update the single global callback with new code for each newly embedded video. In the end, this callback will contain calls to their own instances of my class. You need those try/catch blocks because it throws a false-positive error for all the "other" embeds except the one it's actually executing "right now" - remember this script runs once for every embed on the page. The errors are expected and actually cause no problem, so the try/catch suppresses them.

这样做的目的是让我可以为每个新嵌入的视频动态更新单个全局回调,并使用新代码。最后,这个回调将包含对他们自己的类实例的调用。您需要那些 try/catch 块,因为它会为所有“其他”嵌入引发误报错误,除了“现在”实际执行的嵌入之外 - 请记住,该脚本为页面上的每个嵌入运行一次。错误是预料之中的,实际上不会引起任何问题,因此 try/catch 会抑制它们。

Using the CMS template tag, I create each instance based on the YouTube video ID. I would run into a problem if someone added the same video module more than once, but that's a business problem easily handled since that's not supposed to happen. This allows me to instantiate unique instances of my class over and over for each video.

使用 CMS 模板标签,我根据 YouTube 视频 ID 创建了每个实例。如果有人多次添加相同的视频模块,我会遇到问题,但这是一个很容易处理的业务问题,因为这不应该发生。这允许我为每个视频一遍又一遍地实例化我的类的唯一实例。

The critical part of that script is based on this extremely helpful SO answer: Adding code to a javascript function programmatically

该脚本的关键部分基于这个非常有用的 SO 答案:以编程方式向 javascript 函数添加代码

Here's the actual class. It's commented mostly... We use jQuery, so you'll see one important use of it here in the $.extend() method. I use that as a convenience in the class constructor method, but you could do that with vanilla JS too (JavaScript equivalent of jQuery's extend method) I just find the jQuery easier to read, and since it's available to me, I use it.

这是真正的课堂。它的评论主要是...我们使用 jQuery,因此您将在 $.extend() 方法中看到它的一个重要用途。我在类构造函数方法中使用它是为了方便,但是您也可以使用 vanilla JS(JavaScript 等效于 jQuery 的扩展方法)来做到这一点,我只是发现 jQuery 更易于阅读,而且由于我可以使用它,所以我使用它。

if (typeof Ariba === "undefined") { var Ariba = {}; }
if (typeof Ariba.Innovations === "undefined") { Ariba.Innovations = {}; }
if (typeof Ariba.Innovations.YouTube === "undefined") { Ariba.Innovations.YouTube = {}; }

if (typeof Ariba.Innovations.YouTube.Class === "undefined") {//this script may be embedded more than once - do this to avoid re-processing it on subsequent loads
    Ariba.Innovations.YouTube.Class = function (config) {
        this.static = {
            'ytScriptId': 'js_youtubeFrameAPI',
            'ytScriptUrl': 'https://www.youtube.com/iframe_api'
        };//static configuration.  Will overwrite any other settings with the same name
        this.config = {//optional configuration variables. Will be overridden by instance or static settings with the same name.
            'adobeAnalyticsFired': false
        };
        this.config = $.extend(true, this.config, config);//inserts (destructively!) the instance settings.
        this.config = $.extend(true, this.config, this.static);//inserts (destructively!) the static settings.
        this.config.this = this;
    };

    Ariba.Innovations.YouTube.Class.prototype.Init = function () {
        //Note: have to allow it to write it over an over because calling the API script is what makes YouTube call onYouTubeIframeAPIReady.
        //if (document.getElementById('js_youtubeFrameAPI') === null) { // don't add the script again if it already exists!
        this.config.apiScript = document.createElement('script');
        this.config.apiScript.src = 'https://www.youtube.com/iframe_api';
        this.config.apiScript.id = 'js_youtubeFrameAPI' + this.config.innovYouTubeVideoId;
        this.config.firstScriptTag = document.getElementsByTagName('script')[0];
        this.config.firstScriptTag.parentNode.insertBefore(this.config.apiScript, this.config.firstScriptTag);
        //}
        //else { console.log("iframe script already embedded", this.config.innovYouTubeVideoId); }
    }

    Ariba.Innovations.YouTube.Class.prototype.onYouTubeIframeAPIReady = function (event) {
        //console.log("onYouTubeIframeAPIReady", this.config.innovYouTubeVideoId, arguments);
        var _this = this;
        //console.log(this);
        this.config.ytPlayer = new YT.Player(this.config.innovYouTubeDivId, {
            videoId: this.config.innovYouTubeVideoId,
            events: {
                'onReady': _this.onPlayerReady.bind(_this),
                'onStateChange': _this.onPlayerStateChange.bind(_this)
            }
        });
    }

    Ariba.Innovations.YouTube.Class.prototype.onPlayerReady = function (event) {
        //console.log("onPlayerReady", this.config.innovYouTubeVideoId, event);
    }

    Ariba.Innovations.YouTube.Class.prototype.onPlayerStateChange = function (event) {
        //console.log("onPlayerStateChange", this.config.innovYouTubeVideoId, event, this);
        if (event.data === YT.PlayerState.PLAYING && !this.config.adobeAnalyticsFired) {
            //console.log("YouTube Video is PLAYING!!", this.config.innovYouTubeVideoId);
            this.config.adobeAnalyticsFired = true;
            if (typeof _satellite !== "undefined") {
                window._satellite.data.customVars.adhoc_tracker_val = "Innovations Video: " + this.config.innovYouTubeVideoTitle + " (" + this.config.innovYouTubeVideoId + ")";
                _satellite.track('adhoctrack');
            }
        }
    }
}

A few other notes:

其他一些注意事项:

Keeping scope in the class instance is easy once you get the main global callback problem solved. You just have to add .bind(). For example:

一旦解决了主要的全局回调问题,在类实例中保持作用域就很容易了。你只需要添加 .bind()。例如:

'onReady': _this.onPlayerReady.bind(_this)

You might also see:

您可能还会看到:

var _this = this;

This is so the "this" scope for the instance isn't lost accidentally. Maybe not necessary, but it's a convention I've adopted over the years.

这样实例的“this”范围就不会意外丢失。也许没有必要,但这是我多年来采用的惯例。

Anyway, I've been working on this for a week now, and figured I'd share it with the SO community since it's clear from my looking for answers a lot of others have been searching for solutions to this too.

无论如何,我已经为此工作了一个星期,并认为我会与 SO 社区分享它,因为从我寻找的答案中可以看出,很多其他人也一直在寻找解决方案。

回答by Dihgg

The HTML

HTML

<div data-id="youtubevideoidhere" class="video"></div>
<div data-id="youtubevideoidhere" class="video"></div>
<div data-id="youtubevideoidhere" class="video"></div>

The JS for Videos

视频的 JS

// CREATE VIDEOS "CLASS" to handler videos
var Videos = (function() {
    // VARIABLES
    var $   = jQuery,   // The jquery
    players = [],       // players array (to coltrol players individually)
    queue   = [];       // videos queue (once api is ready, transform this into YT player)

    // Constructor
    function Videos() {}

    // METHODS
    // Add elements to queue
    Videos.prototype.add = function($video) {
        queue.push($video);
    };

    // Load YT API
    Videos.prototype.loadApi = function() {
        // jQuery get script
        $.getScript("//www.youtube.com/iframe_api", function() {
            // once loaded, create the onYouTubeIframeAPIReady function
            window.onYouTubeIframeAPIReady = function() {
                queue.forEach(function($video) {
                    // Create the YT player
                    var player = new YT.Player($video.get(0), {
                        'width': "100%",
                        'height': "100%",
                        'videoId': $video.data("id")
                    });
                    // add to players array
                    players.push(player);
                });
            };
        });
    };

    return Videos;

})();

And then, create videos like this

然后,创建这样的视频

var videos = new Videos();
$('.video').each( function () {
    videos.add( $(this) );
})
videos.loadApi();

回答by souporserious

I needed this same thing in React. Expanding upon Vadim's answer you could do something like the following and add them to an object then create the player if you don't know what the array of players will look like prior.

我在 React 中也需要同样的东西。扩展 Vadim 的答案,您可以执行以下操作并将它们添加到对象中,然后如果您不知道玩家阵列之前的样子,则创建玩家。

const YoutubeAPILoader = {
  _queue: [],
  _isLoaded: false,

  load: function (component) {
    // if the API is loaded just create the player
    if (this._isLoaded) {
      component._createPlayer()
    } else {
      this._queue.push(component)

      // load the Youtube API if this was the first component added
      if (this._queue.length === 1) {
        this._loadAPI()
      }
    }
  },

  _loadAPI: function () {
    // load the api however you like
    loadAPI('//youtube.com/player_api')

    window.onYouTubeIframeAPIReady = () => {
      this._isLoaded = true
      for (let i = this._queue.length; i--;) {
        this._queue[i]._createPlayer()
      }
      this._queue = []
    }
  }
}