YouTubeの動画が何時に終わるか表示

動画が何時に終わるかを表示

// ==UserScript==
// @name        YouTubeの動画が何時に終わるか表示
// @namespace   mikan-megane.youtube-remaining-time
// @match       https://www.youtube.com/*
// @grant       none
// @version     1.4
// @author      mikan-megane
// @description 動画が何時に終わるかを表示
// @license     MIT
// ==/UserScript==

(function() {
    'use strict'; // 厳格モードを有効にする

    /**
     * 時刻文字列 (例: "01:23" または "01:23:45") を秒数に変換するヘルパー関数。
     * @param {string} timeStr - 時刻を表す文字列 (MM:SS または HH:MM:SS 形式)。
     * @returns {number} - 変換された秒数。
     */
    const timeToSeconds = (timeStr) => {
        // 時刻文字列を ':' で分割し、各部分を数値に変換する
        const parts = timeStr.split(':').map(Number);
        // reduce を使用して、時、分、秒を合計秒数に変換する
        // 例: [1, 23, 45] -> (0 * 60 + 1) * 60 + 23) * 60 + 45
        return parts.reduce((acc, part) => acc * 60 + part, 0);
    };

    /**
     * DateオブジェクトからAM/PM形式の時刻文字列を生成するヘルパー関数。
     * 例: 13:30 -> PM 01:30
     * @param {Date} date - フォーマットするDateオブジェクト。
     * @returns {string} - フォーマットされた時刻文字列 (例: "AM 10:05", "PM 01:30")。
     */
    const formatTime = (date) => {
        const hours = date.getHours();
        // 12時間形式に変換し、0時と12時は12として表示する
        const displayHours = (hours % 12) || 12;
        const minutes = date.getMinutes();
        const ampm = hours >= 12 ? 'PM' : 'AM';

        // 時と分を2桁表示 (必要に応じて先頭にゼロを追加)
        const formattedHours = String(displayHours).padStart(2, '0');
        const formattedMinutes = String(minutes).padStart(2, '0');

        return `${ampm} ${formattedHours}:${formattedMinutes}`;
    };

    // 1秒ごとに動画の終了時刻を更新するタイマーを設定
    setInterval(() => {
        // 現在の動画プレイヤーの時刻表示部分をすべて取得
        // 通常はページに1つしかないが、念のためすべてを対象とする
        const wrappers = document.querySelectorAll('.ytp-time-contents');

        for (const wrapper of wrappers) {
            // 動画の総時間と現在の再生時間を取得する要素
            const durationElement = wrapper.querySelector('.ytp-time-duration');
            const currentElement = wrapper.querySelector('.ytp-time-current');

            // 終了時刻を表示する要素を検索
            let finishElement = wrapper.querySelector('.ytp-time-finish');

            // 必要な要素が見つからない場合、または時間が非表示の場合はスキップ
            if (!durationElement || !currentElement ||
                document.defaultView.getComputedStyle(durationElement).display === 'none') {
                // 終了時刻があるなら削除
                if(finishElement) {
                    finishElement.remove();
                }
                continue;
            }

            // finishElement がまだ存在しない場合のみ作成し、DOMに追加
            if (!finishElement) {
                finishElement = document.createElement('span');
                finishElement.classList.add('ytp-time-finish');
                // スタイルを適用して、元の時間表示と区別しやすくする
                Object.assign(finishElement.style, {
                    color: '#ddd', // 薄い灰色
                    marginLeft: '0.4em' // 少し左に余白を追加
                });
                // 総時間の要素の直後に挿入
                durationElement.after(finishElement);
            }

            // 総時間と現在の再生時間を秒数に変換
            const durationSeconds = timeToSeconds(durationElement.textContent);
            const currentSeconds = timeToSeconds(currentElement.textContent);

            // 総時間が0の場合(まだ動画情報が読み込まれていないなど)、スキップ
            if (durationSeconds === 0) {
                continue;
            }

            // 残り秒数を計算
            const remainingSeconds = durationSeconds - currentSeconds;
            // 現在時刻に残り秒数を加算して、終了時刻を計算
            const finishTime = new Date(Date.now() + remainingSeconds * 1000);

            // 終了時刻をフォーマットし、要素のテキストコンテンツを更新
            finishElement.textContent = `・ ${formatTime(finishTime)}`;
        }
    }, 1000); // 1000ミリ秒 (1秒) ごとに実行
})();