YouTube Remaining Time

Show the remaining time of a YouTube video inside the player.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         YouTube Remaining Time
// @namespace    http://the6p4c.com/
// @version      1.3
// @description  Show the remaining time of a YouTube video inside the player.
// @author       The6P4C
// @match        *://www.youtube.com/*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js
// @grant        none
// ==/UserScript==

// ***********************************************************************
// CHANGE TO true TO DISPLAY REMAINING TIME COMPENSATING FOR PLAYBACK RATE
// ***********************************************************************
var SHOW_REAL_TIME_REMAINING = false;

var TIME_KEYS_ORDER = ["days", "hours", "minutes", "seconds"];
var TIME_KEYS_ORDER_REV = [].concat(TIME_KEYS_ORDER).reverse();

function parseTime(timeString) {
    // reversed so seconds first
    var parts = timeString.split(":").reverse();
    var time = {};

    for (var i = 0; i < TIME_KEYS_ORDER_REV.length; ++i) {
        var key = TIME_KEYS_ORDER_REV[i];

        if (i < parts.length) {
            time[key] = parseInt(parts[i]);
        } else {
            time[key] = 0;
        }
    }

    return time;
}

function pad2(n) {
    if (n >= 10) {
        return n;
    } else {
        return "0" + n;
    }
}

function stringifyTime(time) {
    var timeString = "";
    var nonZeroEncountered = false;

    for (var i = 0; i < TIME_KEYS_ORDER.length; ++i) {
        var key = TIME_KEYS_ORDER[i];
        var currentValue = time[key];

        // Only start adding values from the first non zero
        // But... always show minutes, even if they're zero.
        if (currentValue !== 0 || nonZeroEncountered || key == "minutes") {
            // We don't want to pad the first value:
            // If there's hours, don't pad days
            // If there's minutes, don't pad hours
            // If there's seconds, don't pad minutes
            if (nonZeroEncountered) {
                currentValue = pad2(currentValue);
            }

            timeString += currentValue;

            if (key != "seconds") {
                timeString += ":";
            }

            nonZeroEncountered = true;
        }
    }

    return timeString;
}

function subtractTime(a, b) {
    var secondsDiff = a.seconds - b.seconds;
    var minutesDiff = a.minutes - b.minutes;
    var hoursDiff = a.hours - b.hours;
    var daysDiff = a.days - b.days;

    if (secondsDiff < 0) {
        minutesDiff -= 1;
        secondsDiff += 60;
    }

    if (minutesDiff < 0) {
        hoursDiff -= 1;
        minutesDiff += 60;
    }

    if (hoursDiff < 0) {
        daysDiff -= 1;
        hoursDiff += 24;
    }

    return {
        days: daysDiff,
        hours: hoursDiff,
        minutes: minutesDiff,
        seconds: secondsDiff
    };
}

function divideTime(time, divisor) {
    var timeSeconds = time.seconds + ((time.minutes + (time.hours + time.days * 24) * 60) * 60);
    var newTimeSeconds = Math.floor(timeSeconds / divisor);

    return {
        days: Math.floor(newTimeSeconds / (24 * 60 * 60)),
        hours: Math.floor(newTimeSeconds / (60 * 60)),
        minutes: Math.floor(newTimeSeconds / 60),
        seconds: newTimeSeconds % 60
    };
}

function tryGetPlaybackRate($timeDisplay) {
    // Try and find the playback speed from the settings UI
    var $ytdPlayer = $timeDisplay.parents(".html5-video-player");
    if ($ytdPlayer.length != 1) {
        // If we can't find the player, fall back to a "safe" alternative
        return 1;
    }

    $ytdPlayer = $ytdPlayer[0];

    // We can't be sure that this will work - if YouTube changes their player API somehow
    // we don't want our entire script to break. Again, fall back to a "safe" alternative.
    try {
        return $ytdPlayer.getPlaybackRate();
    } catch (e) {
       return 1;
    }
}

function onTimeChange() {
    var $this = $(this);

    // If this event was called in the context of the .ytp-time-duration event,
    // we need to set $timeCurrent not to $this but to the actual
    // .ytp-time-current element.
    var $timeCurrent = $this;
    if (!$this.hasClass("ytp-time-current")) {
        $timeCurrent = $this.parent().find(".ytp-time-current");
    }

    var $timeDisplay = $timeCurrent.parent();

    // For some reason a time will show on a live video, but I don't think it's very meaningful
    if ($timeDisplay.hasClass("ytp-live")) {
        return;
    }

    var $timeDuration = $timeDisplay.parent().find(".ytp-time-duration");

    if ($timeDisplay.attr("____remaining-added") != "____remaining-added") {
        var $timeRemainingWrapper = $("<div style='display: inline-block; width: 5px;'></div><span style='color: #ddd; text-shadow: 0 0 2px rgba(0,0,0,.5)'>(&minus;<span class='____time-remaining'></span>)</span>");
        $timeDuration.after($timeRemainingWrapper);

        $timeDisplay.attr("____remaining-added", "____remaining-added");
    }

    var $timeRemaining = $timeDisplay.find(".____time-remaining");

    var currentTime = parseTime($timeCurrent.text());
    var durationTime = parseTime($timeDuration.text());
    var remainingTime = subtractTime(durationTime, currentTime);

    if (SHOW_REAL_TIME_REMAINING) {
        var playbackRate = tryGetPlaybackRate($timeDisplay);
        var remainingRealTime = divideTime(remainingTime, playbackRate);

        var suffix = playbackRate == 1 ? "" : (" at x" + playbackRate);
        $timeRemaining.text(stringifyTime(remainingRealTime) + suffix);
    } else {
        $timeRemaining.text(stringifyTime(remainingTime));
    }
}

$(document).ready(function() {
    $("body").on("DOMSubtreeModified", ".ytp-time-current", onTimeChange);
    $("body").on("DOMSubtreeModified", ".ytp-time-duration", onTimeChange);
});