Laravel で埋め込み YouTube の操作履歴を保存する

こんにちは❗フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、このブログではいろいろな分野の記事を公開してきましたが、その理由のひとつとして、「開発者だけでなく、経営者サイドに向けて」情報発信がしたいというものがあります。

そして、そんなビジネス的な視点で考えた時にひとつ記事にしてみたいと思うアイデアが浮かんできました。

それは・・・・・・

YouTube 動画の行動履歴を保存する

というものです。

つまり、動画の「どこが飛ばされていて」「どこがよく見られているか」を知ることで、その動画の良し悪しを判断することが狙いです。

また、ホントに「ちゃんと最後まで視聴されているのか」というデータも重要ですよね。

そこで❗

今回はLaravelを使って、YouTube動画の行動履歴を保存できるようにしてみたいと思います。

ぜひ何かの参考になりましたら嬉しいです。😊✨

「新しい試みとして、Vueは使わず、
ピュアなJavaScriptで書いてみました」

開発環境: Laravel 9.x

必要なファイルをつくる

では、まずはLaravelに必要なファイルをつくっていきます。
以下のコマンドを実行してください。

php artisan make:model YoutubeAction -mc
php artisan make:model YoutubeActionDetail -m

すると、「モデル×2」と「マイグレーション×2」と「コントローラー×1」の合計3ファイルが作成されるので、それぞれ中身を変更してください。

モデル

では、モデルからです。

app/Models/YoutubeAction.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class YoutubeAction extends Model
{
    use HasFactory;

    // Relationship
    public function details()
    {
        return $this->hasMany(YoutubeActionDetail::class, 'youtube_action_id', 'id');
    }
}

この中では、youtube_action_detailsテーブルとの「1:多」のリレーションを作っています。

app/Models/YoutubeActionDetail.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class YoutubeActionDetail extends Model
{
    use HasFactory;

    // Constants
    const TYPES = [
        'ended' => '終了',
        'playing' => '再生',
        'paused' => '一時停止',
        'leave' => '離脱',
    ];

    // Accessor
    public function getReadableTimeAttribute()
    {
        return gmdate('H:i:s', $this->time);
    }

    public function getReadableTypeAttribute()
    {
        return data_get(self::TYPES, $this->type);
    }
}

ここでは、表示に関するAccessorを追加しています。

マイグレーション

database/migrations/****_**_**_******_create_youtube_actions_table.php

// 省略

public function up()
{
    Schema::create('youtube_actions', function (Blueprint $table) {
        $table->id();
        $table->string('video_id')->comment('動画ID');
        $table->ipAddress('ip')->comment('IPアドレス');
        $table->timestamps();
    });
}

// 省略

database/migrations/****_**_**_******_create_youtube_action_details_table.php

// 省略

public function up()
{
    Schema::create('youtube_action_details', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('youtube_action_id')->comment('親テーブルID');
        $table->string('type')->comment('操作タイプ');
        $table->integer('time')->comment('再生位置(秒)');
        $table->timestamps();

        $table->foreign('youtube_action_id')->references('id')->on('youtube_actions')->onDelete('cascade');
    });
}

// 省略

親テーブルである、youtube_actionsへの外部キーをつけています。

では、この状態でマイグレーションを実行してみましょう。
以下のコマンドを実行してください。

php artisan migrate

実際のテーブルはこうなりました。

コントローラー

app/Http/Controllers/YoutubeActionController.php

<?php

namespace App\Http\Controllers;

use App\Models\YoutubeAction;
use App\Models\YoutubeActionDetail;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class YoutubeActionController extends Controller
{
    public function index()
    {
        $action = YoutubeAction::with('details')->latest()->first();

        return view('youtube_action.index')->with([
            'action' => $action,
        ]);
    }

    public function create()
    {
        return view('youtube_action.create');
    }

    public function store(Request $request)
    {
        // テストのため、バリデーションは省略しています

        $video_id = $request->video_id;
        $player_actions = $request->player_actions;
        $ip = $request->ip();

        DB::beginTransaction();

        try {

            $action = new YoutubeAction();
            $action->video_id = $video_id;
            $action->ip = $ip;
            $action->save();

            foreach ($player_actions as $player_action) {

                $action_detail = new YoutubeActionDetail();
                $action_detail->youtube_action_id = $action->id;
                $action_detail->type = $player_action['type'];
                $action_detail->time = $player_action['time'];
                $action_detail->save();

            }

            DB::commit();

        } catch (\Exception $e) {

            DB::rollback();
            throw $e;

        }

    }
}

なお、store()は少し複雑なように見えますが、単に以下のことをやっているだけです。

  1. 親テーブル(youtube_actions)にデータを追加
  2. そのデータに関する詳細データ(youtube_action_details)を追加

ビューをつくる

続いて、コントローラー内でセットしたビューをつくっていきましょう。
必要になるのは以下2つです。

  • index: 最新の操作履歴を表示する
  • create: 動画操作履歴を作成する(動画を表示する)

では、それぞれ見ていきましょう。

resources/views/youtube_action/index.blade.php

<!DOCTYPE html>
<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="p-5">
        <div class="mb-3">
            動画ID: <a href="https://www.youtube.com/watch?v={{ $action->video_id }}" target="_blank">{{ $action->video_id }}</a><br>
            保存日時: {{ $action->created_at->format('Y-m-d H:i') }}
        </div>
        <table class="table">
            <thead>
                <tr>
                    <th>再生位置  操作</th>
                </tr>
            </thead>
            <tbody>
            @foreach($action->details as $action_detail)
                <tr>
                    <td>
                        {{ $action_detail->readable_time }}

                        <label class="badge bg-secondary">
                            {{ $action_detail->readable_type }}
                        </label>
                    </td>
                </tr>
            @endforeach
            </tbody>
        </table>
    </div>
</body>
</html>

このビューはシンプルに「最新の動画行動履歴」を表示するためのものです。(テストで確認するために作成しました)

resources/views/youtube_action/create.blade.php

<!DOCTYPE html>
<html>
<head>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="p-5">
        <div id="player"></div>
        <br>
        <a href="https://example.com" class="btn btn-primary mt-3">&#x2611; 動画操作後、別ページへ移動して送信テスト</a>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.26.1/axios.min.js"></script>
    <script src="https://www.youtube.com/iframe_api"></script>
    <script>

        // Data
        const videoId = 'Vg9UPa4plkQ';
        let player = null;
        let playerActions = []; // プレーヤー操作の履歴

        // Methods
        const getCurrentTime = () => {

            const time = player.getCurrentTime();
            return Math.round(time); // 再生位置(小数点はなくす)

        };
        const onStateChange = e => {

            const stateId = e.data;
            const youtubeStates = [ // 取得したい状態タイプ: https://developers.google.com/youtube/iframe_api_reference
                { stateId: YT.PlayerState.ENDED, type: 'ended' },     // 終了
                { stateId: YT.PlayerState.PLAYING, type: 'playing' }, // 再生
                { stateId: YT.PlayerState.PAUSED, type: 'paused' },   // 一時停止
            ];
            const youtubeState = youtubeStates.find(state => {

                return Number(state.stateId) === Number(stateId);

            });

            if(youtubeState !== undefined) { // 取得したい状態がある場合

                const type = youtubeState.type;
                const time = getCurrentTime();

                playerActions.push({ // 行動履歴を貯めていく
                    type,
                    time
                });

            }

        };
        window.onYouTubeIframeAPIReady = () => { // グローバルレベルでないとダメ

            // 動画プレイヤーを準備
            player = new YT.Player('player', {
                height: '390',
                width: '640',
                videoId: videoId,
                playerVars: { 'playsinline': 1 },
                events: {
                    onStateChange: onStateChange // 動画の状態が変更になった場合
                }
            });

        };

        // Events
        window.addEventListener('beforeunload', () => { // ページ移動前にプレーヤー操作の履歴を送信

            if(playerActions.length > 0) {

                playerActions.push({ // 最後にページ離脱を追加
                    type: 'leave',
                    time: getCurrentTime()
                });

                const url = '{{ route('youtube_action.store') }}';
                const params = {
                    video_id: videoId,
                    player_actions: playerActions,
                };
                axios.post(url, params);

            }

        });

    </script>
</body>
</html>

こちらは、実際にYouTube動画の行動履歴を保存するためのコードを含んでいます。

なお、手順としては以下になります。

  1. YouTube のイベントを使って行動履歴を貯めていく
  2. ページを離脱する際にデータを Ajax で送信
  3. Laravel 側でデータを保存

ちなみに1点気をつけていただきたいのが、以下の部分です。

window.onYouTubeIframeAPIReady = () => { // グローバルレベルでないとダメ

    // 省略

};

他のメソッドはconstで定義しているのに、ここではwindowを使っています。

これは、YouTube Player APIがアクセスできるようにグローバル・レベルで定義しないといけないためです。(少しハマリました😅)

※ また、動画は私もたまに観て癒やされている「にゃんこYouTuber」の「もちまる」ちゃんの動画です🐾

では、これで作業は完了です。
お疲れ様でした😊✨

テストしてみる

では、実際にテストしてみましょう❗

まずはブラウザで「https://******/youtube_action/create」へアクセスしてください。

すると、以下のように動画(著作権を考えてぼかしています)とボタンが表示されます。

では、動画を再生し、いろいろと操作をした後にボタンをクリックし、ページ離脱してみます。

すると・・・・・・

はい❗
コードで指定したとおり、example.comへ移動しました。

では、この状態でデータが追加されているかDBをチェックしてみましょう。
まずは親テーブルです。

はい❗
データが追加になっていますね。

続いて、詳細データの方です。

はい❗
こちらもID1のデータがいくつか追加になっていますね。

成功です😊✨

では、最後に「https://******/youtube_aciton」へアクセスして行動履歴を表示してみましょう。

どうなるでしょうか・・・・・・???

はい❗

いろいろと操作したのでちょっと長くなってしまいましたが、これでこのユーザーが動画をどのように見たのかを詳しく知ることができます。

すべて成功です😊👍

企業様へのご提案

今回の機能を使うと、例えばランディングページに設置したYouTube動画がどのように視聴されているのかを詳しく知ることができ、結果、その動画が「ユーザーの獲得に貢献しているかどうか」の判断をすることができます。

また、今回はシンプルに再生や一時停止のデータだけを並べて表示しただけですが、たくさんのデータを集計し、グラフで表示することもできます。

もし、こういった経営判断に関するご相談がございましたら、いつでもお問い合わせください。

お待ちしております😊✨

おわりに

ということで、今回はYouTube動画の操作履歴を集めて保存してみました。

もはやYouTubeはテレビと並ぶメディアと言ってもいいと思いますので、この部分をより活用することでビジネスをさらに加速させることができるんじゃないでしょうか。

かくいう私も、過去テレビっ子だったのが、現在は自室にテレビはなく、「YouTubeっ子」になっていますので、時代の流れを感じずにはいられません。

(ビデオカセットでテレビ番組をタイマー予約してた頃が懐かしいです。なぜかたまに失敗するんですよね😂)

ぜひ皆さんもYouTubeを使った実装をやってみてくださいね。

ではでは〜❗

「時代が違ったら、
YouTuberやってみたかったです😊」

開発のご依頼お待ちしております 😊✨
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします!
このエントリーをはてなブックマークに追加       follow us in feedly  

開発効率を上げるための機材・まとめ