Greasy Fork is available in English.

Youtube Repeat Specific Part

Freely set the start and end time to loop. No other sites used.

À partir de 2019-02-01. Voir la dernière version.

// ==UserScript==
// @name                 Youtube Repeat Specific Part
// @name:zh-TW           Youtube 指定片段循環播放
// @namespace            -
// @version              20190202
// @description          Freely set the start and end time to loop. No other sites used.
// @description:zh-TW    可自由設定影片循環播放,無須轉跳其他網站服務。
// @author               LianSheng
// @match                *://*.youtube.com/*
// @require              https://greasyfork.org/scripts/377302-general-source-code-injection-tool/code/General%20Source%20Code%20Injection%20Tool.js?version=667011
// @grant                GM_setValue
// @grant                GM_getValue
// @license              MIT
// ==/UserScript==

// TODOLIST:
// * 進入頁面判斷
// * 單曲循環分享連結
// * 儲存循環 & 清單
// * 清單匯出匯入
// * 微調循環設定時間
// * 循環接軌預覽 (預計前後各 5 秒)

(function() {
    'use strict';
    // ================【全域設定】================ //
    let tm_repeat_button_icon_size     = "18px";  // 設定按鈕圖示大小
    let tm_repeat_button_size          = "24px";  // 設定按鈕長寬(需大於等於圖示大小)

    let tm_repeat_start_bg_color       = "#9920"; // 開始時間按鈕 背景顏色
    let tm_repeat_end_bg_color         = "#9920"; // 結束時間按鈕 背景顏色
    let tm_repeat_set_bg_color         = "#2d20"; // 執行循環按鈕 背景顏色
    let tm_repeat_unset_bg_color       = "#d550"; // 解除循環按鈕 背景顏色

    let tm_repeat_save_bg_color        = "#22e0"; // [TODOLIST] 儲存目前循環 背景顏色

    let tm_repeat_start_color          = "#ffff"; // 開始時間按鈕 顏色
    let tm_repeat_end_color            = "#ffff"; // 結束時間按鈕 顏色
    let tm_repeat_set_color            = "#ffff"; // 執行循環按鈕 顏色
    let tm_repeat_unset_color          = "#ffff"; // 解除循環按鈕 顏色

    let tm_repeat_save_color           = "#ffff"; // [TODOLIST] 儲存目前循環 顏色

    let tm_repeat_time_check_period    = 33;      // 檢查目前時間是否在指定重複範圍內。單位爲毫秒,預設爲 33 毫秒(約一秒檢查 30 次)。
                                                  // 數值越小將能體驗更流暢的銜接,同時也越吃效能。
    // ================【程式開始】================ //
    let debug_mode = false;

    // 頁面進入點判斷 (TODOLIST)
    let chk_in_watch = 1;

    let css = `
/* 主要區塊 */
#tm_main {
    display: block;
    padding-top: 2px;
}

/* 選取時間區塊 */
#tm_select_time_range {

}

/* 主要按鈕群 */
button.tm_repeat_button {
    cursor: pointer;
    width: ${tm_repeat_button_size} !important;
    height: ${tm_repeat_button_size} !important;
    font-size: ${tm_repeat_button_icon_size};
    border: none;
    outline: none;
    user-select: none;
    opacity: 0.8;
    white-space: nowrap;
}
button.tm_repeat_button:hover {
    opacity: 1;
}
[data-title] {
    font-size: 30px;
    position: relative;
}
:not(.tm_repeat_more_open)[data-title]:hover::before {
    content: attr(data-title);
    position: absolute;
    bottom: 46px;
    display: inline-block;
    padding: 4px 8px;
    border-radius: 2px;
    background: #222;
    color: #fff;
    font-size: 12px;
    font-family: sans-serif;
    white-space: nowrap;
}

/* 更多選項區塊 */
.tm_repeat_more_content {
    position: absolute;
    bottom: 46px;
    display: inline-block;
    padding: 4px 8px;
    border-radius: 2px;
    background: #222;
    color: #fff;
    font-size: 12px;
    font-family: sans-serif;
    white-space: nowrap;
    z-index: 60;
}
`;

    let html_main = `
<!-- 主要按鈕群 -->
<span id="tm_main" style="user-select: none;">
    <span id="tm_debug_current_time"></span>
    <button class="tm_repeat_button" id="tm_repeat_set_start" onclick="setRepeatStart()" data-title="設定循環開始" style="background-color: ${tm_repeat_start_bg_color}; color: ${tm_repeat_start_color};">
        <i class="fas fa-sign-out-alt"></i>
    </button>
    <button class="tm_repeat_button" id="tm_repeat_set_end" onclick="setRepeatEnd()" data-title="設定循環結束" style="background-color: ${tm_repeat_end_bg_color}; font-size: ${tm_repeat_button_icon_size}; color: ${tm_repeat_end_color};">
        <i class="fas fa-sign-in-alt"></i>
    </button>
    <span id="tm_select_time_range">
        <span id="tm_repeat_start_time">0:00:00.0</span>
        <span> ~ </span>
        <span id="tm_repeat_end_time">0:00:00.0</span>
    </span>
    <button class="tm_repeat_button" id="tm_repeat_set" onclick="setRepeat()" data-title="執行循環" style="background-color: ${tm_repeat_set_bg_color}; font-size: ${tm_repeat_button_icon_size}; color: ${tm_repeat_set_color};">
        <i class="fas fa-check"></i>
    </button>
    <button class="tm_repeat_button" id="tm_repeat_unset" onclick="unsetRepeat()" data-title="結束循環" style="background-color: ${tm_repeat_unset_bg_color}; font-size: ${tm_repeat_button_icon_size}; color: ${tm_repeat_unset_color};">
        <i class="fas fa-times"></i>
    </button>
    <button class="tm_repeat_button" id="tm_repeat_more" onclick="tm_more_click()" data-title="更多選項" style="background-color: ${tm_repeat_unset_bg_color}; font-size: ${tm_repeat_button_icon_size}; color: ${tm_repeat_unset_color};">
        <i class="fas fa-bars"></i>
    </button>
    <!-- [TODOLIST] <button class="tm_repeat_button" id="tm_repeat_unset" onclick="saveRepeatDialog()" data-title="儲存循環" style="background-color: ${tm_repeat_save_bg_color}; font-size: ${tm_repeat_button_icon_size}; color: ${tm_repeat_save_color};">
        <i class="fas fa-save"></i>
    </button>-->
</span>

<!-- 更多選項區塊 -->
<div class="tm_repeat_more_content" style="display: none;">
    To be continue...
</div>
`;

    let html_progressBar = `
<!-- [TODOLIST] 進度條:選取範圍 -->
<!--<div class="tm_repeat_area">
    <div style="background-color: #5f5b !important; left: 200px; transform: scaleX(0.38); z-index: 35;" class="ytp-play-progress"></div>
    <div style="background-color: black !important; left: 0px; transform: scaleX(0.7); z-index: 36; class="ytp-play-progress"></div>
</div>-->
`;

    let js = `
var tm_startTime = 0;
var tm_endTime = 0;
var tm_video = document.querySelector("video");
var tm_interval_id = undefined;
var tm_check_url = location.href;

// check url per 0.1s
setInterval(function(){
    let now_url = location.href;
    if(now_url != tm_check_url) {
        changePageInit();
        tm_check_url = now_url;
    }
}, 100)

// [DEBUG] check url per 0.033s
if(${debug_mode}) {
    setInterval(function(){
        document.querySelector("#tm_debug_current_time").innerHTML = readable(tm_video.currentTime, 3);
    }, 33)
}

// 設定開始時間
function setRepeatStart(){
    document.querySelector("#tm_repeat_start_time").innerText = readable(tm_video.currentTime);
    tm_startTime = tm_video.currentTime;

    if(tm_startTime > tm_endTime) {
        tm_endTime = tm_startTime;
        document.querySelector("#tm_repeat_end_time").innerText = readable(tm_video.currentTime);
    }
}

// 設定結束時間
function setRepeatEnd(){
    document.querySelector("#tm_repeat_end_time").innerText = readable(tm_video.currentTime);
    tm_endTime = tm_video.currentTime;

    if(tm_endTime < tm_startTime) {
        tm_startTime = tm_endTime;
        document.querySelector("#tm_repeat_start_time").innerText = readable(tm_video.currentTime);
    }
}

// 執行重複播放
function setRepeat(){
    if(tm_interval_id != undefined) clearInterval(tm_interval_id);

    // check video duration per ${tm_repeat_time_check_period} ms
    tm_interval_id = setInterval(function(){
        if(tm_video.currentTime < tm_startTime || tm_video.currentTime > tm_endTime) tm_video.currentTime = tm_startTime;
    }, ${tm_repeat_time_check_period})
}

// 解除重複播放
function unsetRepeat(){
    clearInterval(tm_interval_id);
    tm_interval_id = undefined;
}

// 點擊【更多設定】按鈕
function tm_more_click() {
    let tm_more_button = document.querySelector("#tm_repeat_more");
    let tm_more_content = document.querySelector(".tm_repeat_more_content");

    if (tm_more_content.style.display === "none") {
        tm_more_content.style.display = "inline-block";
        tm_more_content.style.left = tm_more_button.offsetLeft + "px";
    } else {
        tm_more_content.style.display = "none";
    }

    tm_more_button.classList.toggle("tm_repeat_more_open");
}

// 換頁時初始化所有循環設定
function changePageInit(){
    clearInterval(tm_interval_id);
    tm_interval_id = undefined;
    tm_startTime = 0;
    tm_endTime = 0;
    document.querySelector("#tm_repeat_start_time").innerText = readable(tm_startTime);
    document.querySelector("#tm_repeat_end_time").innerText = readable(tm_endTime);
}

// 可視化:預設精度爲 0.1 秒
function readable(floatNum, precision=1){
    let h = parseInt(floatNum / 3600);
    let m = parseInt((floatNum - h*3600) / 60);
    let s = parseFloat(floatNum - h*3600 - m*60).toFixed(precision);

    if(m < 10) m = "0" + m;
    if(s < 10) s = "0" + s;

    return h + ":" + m + ":" + s;
}
`;

    addStyleLink("https://use.fontawesome.com/releases/v5.7.0/css/all.css");
    addStyle(css, ".ytp-chrome-controls");
    addScript(js, ".ytp-chrome-controls");
    addHTML(html_main, ".ytp-chrome-controls");
    addHTML(html_progressBar, ".ytp-progress-list");
})();