YouTubeでマルチアングル風の演出をつくろう - みかづきブログ その3
以前、2本のYouTubeを切り替えるモック をつくりましたが、それを応用して
「まじですかスカ!」 ( http://www.youtube.com/watch?v=ArfDmSjuU_Y)のPVを全員分同時に再生するサイトをつくってみました。
最初にロードをはさむことによって、以前のものよりも厳密に頭を合わせる努力をしています。
DEMO
※ Playボタンでスタートします
HTML
<div class="box b1"> <div id="player1" class="player"></div> </div> <div class="box b2"> <div id="player2" class="player"></div> </div> <div class="box b3"> <div id="player3" class="player"></div> </div> <div class="box b4"> <div id="player4" class="player"></div> </div> <div class="box b5"> <div id="player5" class="player"></div> </div> <div class="box b6"> <div id="player6" class="player"></div> </div> <div class="box b7"> <div id="player7" class="player"></div> </div> <div class="box b8"> <div id="player8" class="player"></div> </div> <div class="box b9"> <div id="player9" class="player"></div> </div> <div id="load"> <ul class="circle"> <li class="dot"></li> <li class="dot"></li> <li class="dot"></li> <li class="dot"></li> <li class="dot"></li> </ul> <div id="matter"> <div class="inner"></div> </div> </div>
CSS
@charset "UTF-8"; .box { position: absolute; -webkit-transition: all .5s ease; transition: all .5s ease; } .box:after { display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; content: ""; cursor: pointer; } .b1 { top: 0; left: 0; width: 50%; height: 100%; } .b2 { top: 0; left: 50%; width: 25%; height: 25%; } .b3 { top: 0; left: 75%; width: 25%; height: 25%; } .b4 { top: 25%; left: 50%; width: 25%; height: 25%; } .b5 { top: 25%; left: 75%; width: 25%; height: 25%; } .b6 { top: 50%; left: 50%; width: 25%; height: 25%; } .b7 { top: 50%; left: 75%; width: 25%; height: 25%; } .b8 { top: 75%; left: 50%; width: 25%; height: 25%; } .b9 { top: 75%; left: 75%; width: 25%; height: 25%; } .player { position: absolute; width: 100%; height: 100%; }
JavaScript
(function(win) { "use strict"; win.App = win.App || {}; })(this); (function(win, doc, ns) { "use strict"; function EventDispatcher() { this._events = {}; } EventDispatcher.prototype.hasEventListener = function(eventName) { return !!this._events[eventName]; }; EventDispatcher.prototype.addEventListener = function(eventName, callback) { if (this.hasEventListener(eventName)) { var events = this._events[eventName], length = events.length, i = 0; for (; i < length; i++) { if (events[i] === callback) { return; } } events.push(callback); } else { this._events[eventName] = [callback]; } }; EventDispatcher.prototype.removeEventListener = function(eventName, callback) { if (!this.hasEventListener(eventName)) { return; } else { var events = this._events[eventName], i = events.length, index; while (i--) { if (events[i] === callback) { index = i; } } events.splice(index, 1); } }; EventDispatcher.prototype.fireEvent = function(eventName, opt_this, opt_arg) { if (!this.hasEventListener(eventName)) { return; } else { var events = this._events[eventName], copyEvents = _copyArray(events), arg = _copyArray(arguments), length = events.length, i = 0; // eventNameとopt_thisを削除 arg.splice(0, 2); for (; i < length; i++) { copyEvents[i].apply(opt_this || this, arg); } } function _copyArray(array) { var newArray = [], i = 0; try { newArray = [].slice.call(array); } catch(e) { for (; i < array.length; i++) { newArray.push(array[i]); } } return newArray; } }; ns.EventDispatcher = EventDispatcher; })(this, document, App); (function(win, doc, ns) { "use strict"; var instance; function YTPlayer() { var _this = this, _players = [], i = 0; _init(); function _init() { var tag = doc.createElement("script"), firstScriptTag = doc.getElementsByTagName("script")[0]; ns.EventDispatcher.call(_this); tag.src = "https://www.youtube.com/iframe_api"; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); function onYouTubeIframeAPIReady() { _this.fireEvent("READEY_YOUTUBE"); } // export win.onYouTubeIframeAPIReady = onYouTubeIframeAPIReady; } function _onPlayerReady(id, isAutoPlay) { // 動画再生準備完了時イベント _this.fireEvent("READEY_VIDEO", this, ++i); if (isAutoPlay) { this.target.playVideo(); } } function _onPlayerStateChange(evt) { // 動画再生状態変更時イベント if (this.data === 0) { // 再生終了 _this.fireEvent("FINISH_VIDEO", this.target, evt); } if (this.data === 1) { // 動画再生 _this.fireEvent("WATCHED_VIDEO", this.target, evt); } } function build(id, videoId, opt_width, opt_height, opt_isAutoPlay) { var player = new win.YT.Player(id, { width : opt_width || $("#" + id).width(), // 動画幅 height : opt_height || $("#" + id).height(), // 動画高さ videoId : videoId, // 動画ID events : { onReady : function(player) { _onPlayerReady.call(player, id, opt_isAutoPlay); }, onStateChange : function(player) { _onPlayerStateChange.call(player, id); } }, playerVars: { rel : 0, // 関連動画 showinfo : 0, // 動画情報 controls : 0, // コントローラー wmode : "transparent" // z-indexを有効にする } }); player.isFirst = true; _players.push(player); return player; } function _play() { for (i = 0; i < length; i++) { if (i) { _players[i].pauseVideo(); _players[i].seekTo(0); _players[i].playVideo(); } } // 高橋はやい問題 setTimeout(function() { _players[0].pauseVideo(); _players[0].seekTo(0); _players[0].playVideo(); _players[0].setVolume(100); _this.fireEvent("LOADED_VIDEO"); }, 820); // 目合わせ } function load() { var length = _players.length, timer, i; for (i = 0; i < length; i++) { _players[i].setVolume(0); _players[i].playVideo(); } timer = setInterval(function() { var LEVEL = 0.1, ave = 0, sum = 0, i; for (i = 0; i < length; i++) { sum += _players[i].getVideoLoadedFraction(); } ave = sum / length; if (ave > LEVEL) { clearTimeout(timer); _play(); } _this.fireEvent("LOADING_VIDEO", _this, ave / LEVEL); }, 1000); // 1秒ごとに状態を確認 } this.build = build; this.load = load; } YTPlayer.prototype = new ns.EventDispatcher(); YTPlayer.prototype.constructor = YTPlayer; function getInstance() { if (!instance) { instance = new YTPlayer(); } return instance; } ns.YTPlayer = { getInstance: getInstance }; })(this, document, App); (function(win, doc, $, ns) { "use strict"; var ytPlayer = ns.YTPlayer.getInstance(), VIDEO_LIST = [ "c8_5cBeaSkE", "Mjo-JmjQClU", "Igmhp_RhDAc", "Ph7T54fzzoM", "0mLwCOAMqaU", "JM_T-S4D-KI", "iy2G9q9AZsQ", "ktRdjdLr3Xc", "Le0TpO5CK9U" ], length = VIDEO_LIST.length, $matter = $("#matter .inner"); ytPlayer.addEventListener("READEY_YOUTUBE", _handleYTReady); ytPlayer.addEventListener("READEY_VIDEO", _handleReadyVideo); ytPlayer.addEventListener("LOADING_VIDEO", _handleLoadingVideo); ytPlayer.addEventListener("LOADED_VIDEO", _handleLoadedVideo); $(".box").on("click", _handleClickBox); function _handleYTReady() { var i; for (i = 0; i < length; i++) { this.build("player" + (i + 1), VIDEO_LIST[i]); } } function _handleReadyVideo(evt) { if (evt === length) { ytPlayer.load(); } } function _handleLoadingVideo(evt) { $matter.css({width: evt * 100 + "%"}); } function _handleLoadedVideo() { $("#load").fadeOut(); } function _handleClickBox() { $(".b1")[0].className = this.className; this.className = "box b1"; } })(this, document, $, App);
ちょいと解説
ソースを見て頂けるとわかりますが、読み込むビデオもポジションも決め打ちでつくってますが、右側の8本のビデオはクリッカブルになっていて、選択したものが左側のビデオと入れ替わります。(そのためビデオはコントロールできないようになってます)
9本のビデオを同時に流すために、
- 9本ビデオを読み込む
- 9本すべてを無音で一旦再生
- 動画のストリーミングがはじまったらすべてを一時停止
- 読み込みがある程度終わるまで待つ
- 充分なバッファが確保できたら先頭からリスタート
- 1本目のボリュームのみMAXにする
という処理を裏側で行っております。
ただ、高橋愛バージョンのみ他の動画と尺が1秒ちがうという問題があり、そこだけ目合わせです。
今回は以上です。