work.log

元エンジニアの備忘録的ブログ

Video.jsで動画の再生前にプロモーション広告を流してマネタイズする

投稿:

Video.js で再生前にプロモーション広告を流してマネタイズする為のメモ書きです。

Youtube みたく視聴は無料だけどたまに広告が流れるとかそんな奴です。動画配信ってサーバ代が結構かかるので、Youtube 形式のサービスだとその動画も使ってマネタイズしないと中々難しそうです。

他にも、この動画は何回再生されたかを計測したいとか、再生が終了したら別の関連動画も流したいとか色々な要件が出て来ると思うので、Video.js でどのように実装していくかを検証します。

Video.jsで検知したいイベント

動画の再生中に何かアクションを起こすには、その動画が現在どんな状態であるかを把握する必要があります。

HTML5 の <video> 要素では API が用意されているのでこれを使います。それぞれの動画の状態を検知するにはこんな方法となります。

再生 !player.paused()
停止 player.paused()
シーク player.seeking()

これで大体の状態判定が可能となりますが、「最初に再生された時」や、「一時停止した後にまた再生された時」という複雑な判定はこれだけでは無理なので、上記3つの方法を組み合わせて独自に判定するようにしてみます。

Video.jsの状態判定をするスクリプト

(function ($) {

	var player = videojs("video");

	/* 初期状態 */
	var status = {
		current  : "buffering",
		oldstate : "none"
	};

	console.log(status);

	$(".current").empty().append('cur: ' + status.current);
	$(".oldstate").empty().append('old: ' + status.oldstate);

	/* 再生、停止が実行された時 */
	player.on(["play", "pause"], function(e) {

		GetPlayerStatus();
		console.log(status);

		$(".current").empty().append('cur: ' + status.current);
		$(".oldstate").empty().append('old: ' + status.oldstate);

	});

	/* 動画が終了した時 */
	player.on("ended", function() {
		$(".current").empty().append('cur: ended');
	});

	/* 動画の状態を判定 */
	function GetPlayerStatus() {

		var is = {
			Play    : !player.paused(),
			Paused  : player.paused(),
			Seeking : player.seeking()
		};

		/* 一つ前の状態を記録 */
		status.oldstate = status.current;

		/* シーク */
		if (is.Seeking) {

			status.current = "seeking";

		/* 再生 */
		} else if (is.Play) {

			status.current = "play";

		/* 停止 */
		} else if (is.Paused) {

			status.current = "paused";

		} else {

			status.current = "unknown";
		}

		return;

	}

})(jQuery);

最初の再生を判定するにはその直前の状態を知っておく必要があるので、現在と直前のステータスを変数に入れて管理します。

JWPlayer の API にはこのような機能があるのでそれを真似てみました。

Video.jsの状態判定デモ

上記の判定スクリプトを利用したデモが下記になります。

動画の左上に現在の状態と、その直前の状態を表示するようにしてみました。

console.log() で状態を吐き出しているので、ウェブコンソールを使えば詳細が見られると思いますが、シーク時は一度で下記の状態変化を経ているようです。

  1. { Play: false, Paused: true, Seeking: true }
  2. { Play: true, Paused: false, Seeking: true }

実質、play と Paused が同時に呼ばれてしまうみたいなので、最初に seekinng を判定してシーク時はそのどちらにもに引っかからないようにしています。

Video.jsでプロモーション動画を差し込む

上記スクリプトで細かい状態が判定出来るようになったので、これを使って本編の前にプロモーション動画を差し込んでみます。

プロモーション動画は適当な静止画を10秒の mp4 にしたものを使い下記デモを作ってみました。

プロモーション動画のデモ

最初にプロモーション動画が流れ、5秒後にスキップボタンを表示するようにしています。

本編の動画はスキップボタンを押すか、プロモーション動画が終われば配信される仕組みです。

Video.jsにプロモーション動画を実装するコード

このプロモーション動画はこのように実装しています。

# promotion.js

(function ($) {

	var player = videojs("video");

	var status = {
		current  : "buffering",
		oldstate : "none"
	};

	player.on(["play", "pause"], function(e) {

		GetPlayerStatus();

		/* 一番最初の再生は buffering => play のステータス変化から判定する */
		if (status.current == "play" && status.oldstate == "buffering") {

			/* 5秒後にスキップボタンを表示 */
			$.wait(5000).done(function(){
				$(".skip").css("display", "block");
			});

		}

	});

	/* スキップボタンが押された時の処理 */
	$(".skip").on("click", function(e) {
		$(".skip").css("display", "none");
		player.paused();
		player.src({ type: "application/x-mpegURL", src: "video.m3u8" });
		player.load();
		player.play();
	});

	/* プロモーション動画が再生終了した時の処理 */
	player.on("ended", function() {
		var promotion = player.currentSrc().match(/promotion\.mp4/);
		if (promotion != null) {
			$(".skip").css("display", "none");
			player.src({ type: "application/x-mpegURL", src: "video.m3u8" });
			player.play();
		}
	});

	$.wait = function(msec) {
		var d = new $.Deferred;
		setTimeout(function(){ d.resolve(msec); }, msec);
		return d.promise();
	};

	function GetPlayerStatus() {

		var is = {
			Play    : !player.paused(),
			Paused  : player.paused(),
			Seeking : player.seeking()
		};

		status.oldstate = status.current;

		if (is.Seeking) {

			status.current = "seeking";

		} else if (is.Play) {

			status.current = "play";

		} else if (is.Paused) {

			status.current = "paused";

		} else {

			status.current = "unknown";
		}

		return;

	}

})(jQuery);

31行目player.load() がポイント。

iOS 以外だと無くても src でセットした動画を再生できるが、iOS の場合はこれで読み込まないと .play() が動作しない。

# promotion.css

スキップボタン部分のスタイルシート。

.skip {
	display: none;
	position: absolute;
	z-index: 100;
	bottom: 40px;
	right: 10px;
	width: 150px;
	box-sizing: border-box;
	padding: 5px;
	text-align: center;
	cursor: pointer;
	font-size: 15px;
	font-weight: bold;
	border: 1px solid #00008b;
	color: #fff;
	background-color: #0000cd;
}

# html

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex,nofollow">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>HTTP Live Streaming Demo</title>
<link href="js/video-js/video-js.min.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
<link href="css/promotion.css" rel="stylesheet">
<script src='js/video-js/video.min.js'></script>
<script src='js/videojs-contrib-media-sources.min.js'></script>
<script src='js/videojs-contrib-hls.min.js'></script>
<script>
videojs.options.flash.swf = "js/video-js/video-js.swf";
var ua = navigator.userAgent.toLowerCase();
var isIE = (ua.indexOf('msie') > -1) && (ua.indexOf('opera') == -1) || (ua.indexOf('trident/7') > -1);
if (isIE) {
    videojs.options.hls.mode = 'flash';
}
</script>
</head>
<body>
<video id="video" class="video-js vjs-default-skin vjs-big-play-centered" poster='thumbnail.jpg' controls preload="auto" data-setup="{}">
<source src="promotion.mp4" type="video/mp4">
</video>
<p class="skip">Skip &gt;&gt;</p>
<script src="js/promotion.js"></script>
</body>
</html>

プロモーション動画を差し込む時の問題点

上記でそれっぽい感じにプロモーション動画を差し込む事が出来ましたが、この方法だと一部問題点もあります。

まず一つ目はフルスクリーンだとスキップボタンが出ない事。なのでこの方法だとインラインモードでしか使えません。

次に iPhone についてですが、こちらは動画の再生ボタンを押すといきなりフルスクリーンモードに変わってしまうので、そうなるともうお手上げです。

一応、プロモーション動画が終われば本編が始まりますが、プロモーション動画を流す時はそれ用に何らかのアクション機能を持たせないと、テレビCMみたく効果があるのか無いのかがわからない広告になってしまうので、そもそもスポンサーを見つけるのが難しいのかもしれません。Web の場合、コンバージョンがシビアに設定されますからねぇ…

もしかして iPhone の Safari で Youtube を見ると広告が出ないのってこの為かもしれません。

いずれにせよ、この辺りは今回までに調べ切れなかった部分なので、後でじっくりと検証してみる予定です。

その他Video.jsのAPIメモ

最後は上記以外で使いそうな Video.js の API をメモします。

フルスクリーンを解除

player.exitFullscreen();

他に .exitFullWindow() というのもあるがコレが良くわからず。

動画の再生時間を取得

/* duration  => sec */
var duration = player.duration();

動画全体の再生時間が秒単位で取れます。

現在の再生位置を取得

/* current => sec */
var current = player.currentTime();

現在の再生位置が秒単位で取れます。

また何か発見したら書きます。

参考にしたページ
Video.js API Documentation (Player)