みかづきブログ その3

3ヶ月つづけてみました。

続・YouTubeでマルチアングル風の演出をつくろう


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本のビデオを同時に流すために、

  1. 9本ビデオを読み込む
  2. 9本すべてを無音で一旦再生
  3. 動画のストリーミングがはじまったらすべてを一時停止
  4. 読み込みがある程度終わるまで待つ
  5. 充分なバッファが確保できたら先頭からリスタート
  6. 1本目のボリュームのみMAXにする

という処理を裏側で行っております。

ただ、高橋愛バージョンのみ他の動画と尺が1秒ちがうという問題があり、そこだけ目合わせです。



今回は以上です。