Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- javascript: void((() => {
- /* Video Controller Bookmarklet 2021-08-15 by むふー@4chouyou */
- /* タイムスタンプのフォーマットの設定 */
- const HIDE_HOUR_IF_ZERO = true; /* hが0のときに省略する */
- const APPLY_PADDING_TO_MIN = true; /* hが省略されたときもmの位の0埋めを行う */
- const APPLY_PADDING_TO_HOUR = false; /* hの位の0埋めを行う */
- const COPY_WITH_SPACE = true; /* 末尾に半角スペースを足してコピーする */
- /* スタイルの設定 */
- const PANEL_MIN_WIDTH = '342px'; /* パネルの最小幅 */
- const TIMESTAMP_EDITOR_HEIGHT = '200px'; /* エディターの高さ */
- const FONT_SIZE = '72px'; /* 再生時間表示のフォントサイズ */
- const FONT_COLOR = 'rgba(255, 255, 255, 1)'; /* フォントカラー */
- const BG_COLOR = 'rgba(28, 28, 28, 0.5)'; /* 背景色 */
- const BG_BLUR_RADIUS = '3px'; /* 背景のぼかし */
- const OPACITY = '1'; /* 透明度 */
- /* その他の設定 */
- const PANEL_EMBEDDED = true; /* 最初からパネルがプレイヤーに埋め込まれる */
- const TOOLBAR_COLLAPSED = true; /* 最初からツールバーが折りたたまれる */
- const EDITOR_COLLAPSED = false; /* 最初からエディターが折りたたまれる */
- const USE_CLIPBOARD_READING = false; /* クリップボードを読み込む機能を有効にする */
- const GET_TIME_FROM_TEXT_ELEMENT = true; /* テキスト要素から再生時間を取得する */
- const TIME_SELECTOR = [ /* 再生時間を取得する要素の指定 */
- 'span[class^="___elapsed-time___"] span:first-child', /*ニコニコ生放送*/
- 'div[class^="___elapsed-time___"] span[class^="___time-score___"] span[class^="___value___"]', /*ニコニコ生放送*/
- '.volume-btn-wrapper + div .label + span', /*Mildomライブ*/
- 'span[class^="ControlBar__Time"]', /*OPENREC*/
- '.bilibili-live-player-video-controller-duration-btn > div > span', /*bilibiliライブ*/
- '.tip-wrap > div > .text', /*bilibiliライブ*/
- '.live-time', /*twitch*/
- '#updatetimer', /*ツイキャス*/
- '.messageAndTime .timeBox' /*LINE LIVE*/
- ];
- const PARENT_SELECTOR = [ /* パネルを埋め込むプレイヤー要素の指定 */
- '.html5-video-player', /*YouTube*/
- '.PlayerContainer', /*ニコニコ動画*/
- 'div[class^="___player-display-screen___"]', /*ニコニコ生放送*/
- '.room__player-wrapper', /*Mildomアーカイブ*/
- '.room_player_container > .wrapper', /*Mildomライブ*/
- '.video-player-wrapper', /*OPENREC*/
- '.bilibili-player-video-wrap', /*bilibili動画*/
- '.bilibili-live-player' /*bilibiliライブ*/
- ];
- const PLAYBACK_RATE = [0.1, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4, 5, 7.5, 10]; /* 再生速度のプリセット */
- /* 設定ここまで */
- let currentURL = document.URL;
- let timeoutId, intervalId;
- let currentTimeOffset;
- let repeatStart = null;
- let repeatEnd = null;
- let timeTable, lastIndex;
- let placeholderText = `Ctrl+Shift+Enter: 最後の行にタイムスタンプを挿入
- Ctrl+Shift+/: カーソル位置にタイムスタンプを挿入
- Ctrl+Shift+C: 現在のタイムスタンプをコピー
- Ctrl+Shift+↑ or ↓: 選択範囲のタイムスタンプの秒数を1増減
- Ctrl+Shift+Z or X: ±1秒シーク
- Ctrl+Shift+A or S: ±5秒シーク
- Ctrl+Shift+Space: 再生/一時停止 `;
- let playbackRateIndex = PLAYBACK_RATE.indexOf(1);
- let backupTrigger = false;
- let parentFrame;
- let video = getVideo();
- if(!video) return;
- let panel = document.querySelector('#bml-videoctrl-panel');
- if(panel) panel.remove();
- panel = document.createElement('div');
- panel.id = 'bml-videoctrl-panel';
- panel.style.right = '10px';
- panel.style.top = '10px';
- panel.setAttribute('active', 'current-time');
- panel.setAttribute('ctrl', false);
- panel.innerHTML = `
- <div class="panel-buttons flex">
- <div id="bml-videoctrl-drag-handle" class="drag-handle flex-item"></div>
- <button class="panel-button" command="toggle-embed" title="Toggle embed mode">[< >]</button>
- <button class="panel-button" command="minimize" title="Minimize">[ _ ]</button>
- <button class="panel-button" command="close" title="Close">[ x ]</button>
- </div>
- <div class="panel-content">
- <div class="text-main" title="Copy timestamp">
- <div class="current-time">
- <span class="text-before-point"></span><span class="text-point smallest">.</span><span class="text-after-point smallest"></span>
- <span class="current-playback-rate"></span>
- </div>
- <div class="copied smaller"></div>
- </div>
- <div class="flex">
- <button class="toggle-toolbar-button flex-item" command="toggle-toolbar" title="Toggle toolbar"><span class="material-icons">menu</span></button>
- </div>
- <div class="toolbar-container">
- <div class="toolbar flex">
- <button class="toolbar-button flex-item" command="seek" value="-30" title="Seek -30 sec.">-30<span class="smaller">s</span></button>
- <button class="toolbar-button flex-item" command="seek" value="-5" title="Seek -5 sec.">-5<span class="smaller">s</span></button>
- <button class="toolbar-button flex-item" command="seek" value="-1" title="Seek -1 sec.">-1<span class="smaller">s</span></button>
- <button class="toolbar-button flex-item" command="seek" value="1" title="Seek 1 sec.">+1<span class="smaller">s</span></button>
- <button class="toolbar-button flex-item" command="seek" value="5" title="Seek 5 sec.">+5<span class="smaller">s</span></button>
- <button class="toolbar-button flex-item" command="seek" value="30" title="Seek 30 sec.">+30<span class="smaller">s</span></button>
- <button class="toolbar-button flex-item use-clipboard" command="paste-time" title="Paste time"><span class="material-icons">content_paste</span></button>
- </div>
- <div class="toolbar flex">
- <button class="toolbar-button flex-item" command="repeat" value="start" title="AB repeat - Set A"><span class="material-icons">repeat</span><span class="smaller">A</span></button>
- <button class="toolbar-button flex-item" command="repeat" value="end" title="AB repeat - Set B"><span class="material-icons">repeat</span><span class="smaller">B</span></button>
- <button class="toolbar-button flex-item" command="playback-rate-decr" title="Decrease playback rate"><span class="material-icons">fast_rewind</span></button>
- <button class="toolbar-button flex-item" command="playback-rate-incr" title="Increase playback rate"><span class="material-icons">fast_forward</span></button>
- <button class="toolbar-button flex-item" command="prevframe" title="Previous frame">-1<span class="smaller">f</span></button>
- <button class="toolbar-button flex-item" command="nextframe" title="Next frame">+1<span class="smaller">f</span></button>
- <button class="toolbar-button flex-item" command="screenshot"><span class="material-icons" title="Take a screen shot">camera_alt</span></button>
- <button class="toolbar-button flex-item" command="toggle-timestamp-editor" title="Toggle editor"><span class="material-icons">edit</span></button>
- </div>
- <div class="timestamp-editor-container" mode="edit">
- <div class="timestamp-editor">
- <textarea class="content" placeholder="${placeholderText}"></textarea>
- <div class="toolbar flex">
- <button class="toolbar-button fix-item" command="timestamp-anchor-offset-editor" value="-1" title="-1sec">-1<span class="smaller">s</span></button>
- <button class="toolbar-button fix-item" command="timestamp-anchor-offset-editor" value="1" title="+1sec">+1<span class="smaller">s</span></button>
- <div class="flex-item"></div>
- <button class="toolbar-button fix-item" command="insert-timestamp" title="Insert timestamp"><span class="material-icons">add_circle</span></button>
- <div class="flex-item"></div>
- <button class="toolbar-button fix-item" command="copy-all"><span class="material-icons" title="Copy all">copy_all</span></button>
- <button class="toolbar-button fix-item" command="view-mode"><span class="material-icons" title="Done">done</span></button>
- </div>
- </div>
- <div class="timestamp-viewer">
- <div class="content"></div>
- <div class="toolbar flex">
- <button class="toolbar-button fix-item" command="timestamp-anchor-offset-viewer" value="-1" title="-1sec">-1<span class="smaller">s</span></button>
- <button class="toolbar-button fix-item" command="timestamp-anchor-offset-viewer" value="1" title="+1sec">+1<span class="smaller">s</span></button>
- <div class="flex-item"></div>
- <button class="timestamp-prev toolbar-button fix-item" command="timestamp-jump" title="Previous timestamp"><span class="material-icons">chevron_left</span></button>
- <button class="timestamp-replay toolbar-button fix-item" command="timestamp-jump" title="Current timestamp"><span class="material-icons">replay</span></button>
- <button class="timestamp-next toolbar-button fix-item" command="timestamp-jump" title="Next timestamp"><span class="material-icons">chevron_right</span></button>
- <div class="flex-item"></div>
- <button class="toolbar-button fix-item" command="copy-all"><span class="material-icons" title="Copy all">copy_all</span></button>
- <button class="toolbar-button fix-item" command="edit-mode"><span class="material-icons" title="Edit">edit</span></button>
- </div>
- </div>
- <div class="timestamp-backup">
- </div>
- </div>
- <div class="repeat-toolbar toolbar flex" hidden>
- <div class="repeat-text flex-item"></div>
- <button class="toolbar-button fix-item" command="repeat" value="clear" title="Remove"><span class="material-icons">delete</span></button>
- </div>
- </div>
- </div>
- <a class="screenshot-dllink" target="_blank" hidden></a>
- `;
- document.body.appendChild(panel);
- loadBackup();
- toggleEmbed(PANEL_EMBEDDED);
- toggleToolbar(TOOLBAR_COLLAPSED);
- let style = document.querySelector('#bml-videoctrl-style');
- if(!style) {
- style = document.createElement('style');
- style.id = 'bml-videoctrl-style';
- style.type = 'text/css';
- document.head.appendChild(style);
- var isDrag = false;
- var targetPanel, startX, startY, panelRight, panelTop, panelWidth, panelHeight, parentWidth, parentHeight;
- document.addEventListener('mousedown', e => {
- if(e.target.classList.contains('drag-handle')) {
- e.preventDefault();
- let panel = document.querySelector('#bml-videoctrl-panel');
- targetPanel = e.target.id;
- isDrag = true;
- startX = e.clientX;
- startY = e.clientY;
- panelRight = parseInt(panel.style.right);
- panelTop = parseInt(panel.style.top);
- panelWidth = panel.clientWidth;
- panelHeight = panel.clientHeight;
- if(panel.getAttribute('embedded') == 'true') {
- parentWidth = panel.parentNode.clientWidth;
- parentHeight = panel.parentNode.clientHeight;
- }else {
- parentWidth = document.documentElement.clientWidth;
- parentHeight = document.documentElement.clientHeight;
- }
- };
- });
- document.addEventListener('mouseup', e => {
- isDrag = false;
- });
- document.addEventListener('mousemove', e => {
- if(isDrag && targetPanel == 'bml-videoctrl-drag-handle') {
- e.preventDefault();
- let panel = document.querySelector('#bml-videoctrl-panel');
- panel.style.right = Math.min(parentWidth - panelWidth, Math.max(0, panelRight - (e.clientX - startX))) + 'px';
- panel.style.top = Math.min(parentHeight - panelHeight, Math.max(0, panelTop + (e.clientY - startY))) + 'px';
- }
- });
- if(EDITOR_COLLAPSED) {
- let editorContainer = document.querySelector('#bml-videoctrl-panel .timestamp-editor-container');
- editorContainer.hidden = true;
- }
- if(USE_CLIPBOARD_READING) {
- navigator.clipboard.readText().then(s => {});
- var isCtrlDown = false;
- document.addEventListener('keydown', e => {
- let panel = document.querySelector('#bml-videoctrl-panel');
- if(!panel) return;
- if(e.ctrlKey && !isCtrlDown) {
- isCtrlDown = true;
- panel.setAttribute('ctrl', true);
- }
- });
- document.addEventListener('keyup', e => {
- let panel = document.querySelector('#bml-videoctrl-panel');
- if(!panel) return;
- if(!e.ctrlKey && isCtrlDown) {
- isCtrlDown = false;
- panel.setAttribute('ctrl', false);
- }
- });
- }else {
- let useClipboards = document.querySelectorAll('#bml-videoctrl-panel .use-clipboard');
- useClipboards.forEach(element => {
- element.hidden = true;
- });
- }
- }
- style.innerHTML = `
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400&display=swap');
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap');
- @font-face {
- font-family: 'Material Icons';
- font-style: normal;
- font-weight: 400;
- src: url(https://fonts.gstatic.com/s/materialicons/v83/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
- }
- #bml-videoctrl-panel .material-icons {
- font-family: 'Material Icons';
- font-weight: normal;
- font-style: normal;
- font-size: 24px;
- line-height: 1;
- letter-spacing: normal;
- text-transform: none;
- display: inline-block;
- white-space: nowrap;
- word-wrap: normal;
- direction: ltr;
- -webkit-font-feature-settings: 'liga';
- -webkit-font-smoothing: antialiased;
- }
- #bml-videoctrl-panel [hidden] {
- display: none !important;
- }
- #bml-videoctrl-panel {
- --bml-toolbar-height: 36px;
- position: absolute;
- z-index: 2147483647;
- background-color: ${BG_COLOR};
- border-radius: 4px;
- font-family: Roboto;
- opacity: ${OPACITY};
- backdrop-filter: blur(${BG_BLUR_RADIUS});
- }
- .ytp-player-minimized #bml-videoctrl-panel {
- display: none;
- }
- #bml-videoctrl-panel ::-webkit-scrollbar {
- width: 10px;
- background-color: rgba(0,0,0,.1);
- }
- #bml-videoctrl-panel ::-webkit-scrollbar-thumb {
- background-color: rgba(255,255,255,.25);
- border: none;
- border-radius: 5px;
- }
- #bml-videoctrl-panel ::-webkit-scrollbar-track {
- background-color: rgba(0,0,0,.1);
- }
- #bml-videoctrl-panel[embedded="false"] {
- position: fixed;
- }
- #bml-videoctrl-panel[ctrl="false"] .shown-on-ctrl,
- #bml-videoctrl-panel[ctrl="true"] .hidden-on-ctrl {
- display: none;
- }
- #bml-videoctrl-panel button {
- outline: none;
- }
- #bml-videoctrl-panel .flex {
- display: flex;
- }
- #bml-videoctrl-panel .flex-item {
- flex-grow: 1;
- }
- #bml-videoctrl-panel .fix-item {
- flex-basis: var(--bml-toolbar-height);
- flex-shrink: 0;
- }
- #bml-videoctrl-panel .smaller {
- font-size: 75%;
- }
- #bml-videoctrl-panel .smallest {
- font-size: 50%;
- }
- #bml-videoctrl-panel .panel-buttons {
- box-sizing: border-box;
- padding: 4px;
- float: right;
- }
- #bml-videoctrl-panel .drag-handle {
- border-radius: 4px;
- background-color: rgba(255, 255, 255, .1);
- cursor: move;
- }
- #bml-videoctrl-panel .panel-button {
- width: initial;
- margin-left: 5px;
- padding: 0;
- border: none;
- background-color: transparent;
- color: ${FONT_COLOR};
- font-family: Roboto;
- font-size: 11px;
- cursor: pointer;
- }
- #bml-videoctrl-panel .panel-buttons {
- width: 100%;
- }
- #bml-videoctrl-panel .toggle-toolbar-button {
- width: 100%;
- height: 24px;
- padding: 0;
- border: none;
- background-color: transparent;
- color: ${FONT_COLOR};
- font-family: Roboto;
- font-size: 20px;
- font-weight: 700;
- text-align: center;
- line-height: 24px;
- opacity: .5;
- transition: opacity .2s ease-in-out;
- cursor: pointer;
- }
- #bml-videoctrl-panel .toggle-toolbar-button:hover {
- opacity: 1;
- }
- #bml-videoctrl-panel .panel-content {
- padding: 5px;
- min-width: ${PANEL_MIN_WIDTH};
- }
- #bml-videoctrl-panel .text-main {
- margin: 16px 16px 0;
- height: ${FONT_SIZE};
- color: ${FONT_COLOR};
- font-size: ${FONT_SIZE};
- font-weight: 700;
- line-height: ${FONT_SIZE};
- text-align: center;
- cursor: pointer;
- user-select: none;
- opacity: .8;
- transition: opacity .2s ease-in-out;
- }
- #bml-videoctrl-panel .text-main:hover {
- opacity: 1;
- }
- #bml-videoctrl-panel .text-main > div {
- visibility: collapse;
- height: 0;
- }
- #bml-videoctrl-panel[active="current-time"] .current-time,
- #bml-videoctrl-panel[active="paste-time"] .paste-time,
- #bml-videoctrl-panel[active="copied"] .copied {
- visibility: visible;
- height: 100%;
- }
- #bml-videoctrl-panel .current-playback-rate {
- position: absolute;
- top: 32px;
- right: 8px;
- font-size: 16px;
- line-height: 16px;
- }
- #bml-videoctrl-panel .toolbar-container {
- border-top: 1px solid rgba(255, 255, 255, .2);
- }
- #bml-videoctrl-panel .toolbar:not([hidden]) {
- height: var(--bml-toolbar-height);
- }
- #bml-videoctrl-panel .toolbar-button {
- width: 100%;
- border: none;
- background-color: transparent;
- color: ${FONT_COLOR};
- font-family: Roboto;
- font-size: 20px;
- font-weight: 700;
- text-align: center;
- white-space: nowrap;
- opacity: .5;
- transition: opacity .2s ease-in-out;
- cursor: pointer;
- }
- #bml-videoctrl-panel .toolbar-button[disabled] {
- cursor: default;
- }
- #bml-videoctrl-panel .toolbar-button:not([disabled]):hover {
- opacity: 1;
- }
- #bml-videoctrl-panel .toolbar-button .material-icons {
- margin: 5px 0;
- }
- #bml-videoctrl-panel .timestamp-editor-container {
- border-top: 1px solid rgba(255, 255, 255, .2);
- }
- #bml-videoctrl-panel .timestamp-editor-container > div {
- display: none;
- }
- #bml-videoctrl-panel .timestamp-editor-container[mode="edit"] .timestamp-editor,
- #bml-videoctrl-panel .timestamp-editor-container[mode="view"] .timestamp-viewer {
- display: flex;
- flex-direction: column;
- }
- #bml-videoctrl-panel .timestamp-editor-container .content {
- width: 0;
- min-width: 100%;
- height: ${TIMESTAMP_EDITOR_HEIGHT};
- overflow: auto;
- font-size: 12px;
- font-family: Roboto;
- font-weight: 400;
- line-height: 16px;
- white-space: pre-wrap;
- box-sizing: border-box;
- color: ${FONT_COLOR};
- border: none;
- padding: 2px;
- resize: none;
- user-select: text;
- }
- #bml-videoctrl-panel .timestamp-editor .content {
- background-color: rgba(0, 0, 0, .25);
- }
- #bml-videoctrl-panel .timestamp-editor .content::placeholder {
- color: ${FONT_COLOR};
- opacity: .5;
- }
- #bml-videoctrl-panel .timestamp-anker {
- color: ${FONT_COLOR};
- text-decoration: none;
- }
- #bml-videoctrl-panel .timestamp-anker:hover {
- text-decoration: underline;
- }
- #bml-videoctrl-panel .timestamp-anker.current {
- font-weight: bold;
- }
- #bml-videoctrl-panel .content-row {
- display: inline-block;
- width: 100%;
- margin: 0 -2px;
- padding: 0 2px;
- }
- #bml-videoctrl-panel .content-row.current {
- background-color: rgba(255, 255, 255, .2);
- }
- #bml-videoctrl-panel .repeat-toolbar .repeat-text {
- font-size: 16px;
- font-family: Roboto;
- font-weight: 400;
- line-height: var(--bml-toolbar-height);
- color: ${FONT_COLOR};
- text-align: center;
- opacity: .5;
- }`;
- setEventListener();
- intervalId = setInterval(timeupdate, 1 / 30);
- function setEventListener() {
- let textMain = document.querySelector('#bml-videoctrl-panel .text-main');
- textMain.addEventListener('click', e => {
- let ft = copyTimestamp();
- let copied = document.querySelector('#bml-videoctrl-panel .copied');
- copied.textContent = '📋"' + ft + '"';
- panel.setAttribute('active', 'copied');
- if(timeoutId) {
- clearTimeout(timeoutId);
- }
- timeoutId = setTimeout(() => {
- if(panel.getAttribute('active') == 'copied') {
- panel.setAttribute('active', 'current-time');
- }
- }, 2000);
- });
- let viewer = document.querySelector('#bml-videoctrl-panel .timestamp-viewer .content');
- viewer.addEventListener('click', e => {
- if(e.target.classList.contains('timestamp-anker')) {
- e.preventDefault();
- let video = getVideo();
- if(video) {
- let targetTime = parseInt(e.target.getAttribute('time'));
- video.currentTime = targetTime;
- }
- }
- });
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- editor.addEventListener('keydown', e => {
- let keyCode = e.keyCode;
- backupTrigger = true;
- if(e.ctrlKey && e.shiftKey) {
- switch(keyCode) {
- case 38: { /* ↑: offset 1sec */
- e.preventDefault();
- setEditorTimestampAnchorOffset(1);
- break;
- }
- case 40: { /* ↓: offset -1sec */
- e.preventDefault();
- setEditorTimestampAnchorOffset(-1);
- break;
- }
- case 13: { /* Enter: insert timestamp (end) */
- e.preventDefault();
- insertTimestamp(true);
- break;
- }
- case 191: { /* /: insert timestamp */
- e.preventDefault();
- insertTimestamp();
- break;
- }
- case 67: { /* C: copy timestamp */
- e.preventDefault();
- copyTimestamp();
- break;
- }
- case 90: { /* Z: seek -1sec */
- e.preventDefault();
- seekVideo(-1, true);
- break;
- }
- case 88: { /* X: seek 1sec */
- e.preventDefault();
- seekVideo(1, true);
- break;
- }
- case 65: { /* A: seek -5sec */
- e.preventDefault();
- seekVideo(-5, true);
- break;
- }
- case 83: { /* S: seek 5sec */
- e.preventDefault();
- seekVideo(5, true);
- break;
- }
- case 32: { /* Space: play/pause video */
- e.preventDefault();
- let video = getVideo();
- if(video.paused) {
- video.play();
- }else {
- video.pause();
- }
- break;
- }
- }
- }
- e.stopImmediatePropagation();
- });
- let buttons = document.querySelectorAll('#bml-videoctrl-panel button');
- buttons.forEach(button => {
- button.addEventListener('click', e => {
- let command = button.getAttribute('command');
- switch(command) {
- case 'seek': {
- let sec = parseFloat(button.value);
- seekVideo(sec, true);
- break;
- }
- case 'paste-time': {
- let video = getVideo();
- navigator.clipboard.readText().then(string => {
- let targetTime = getTimeFromString(string);
- if(targetTime) {
- video.currentTime = targetTime;
- }
- });
- break;
- }
- case 'prevframe': {
- let video = getVideo();
- if(!video.paused) {
- video.pause();
- }
- seekVideo(-1 / 30, false);
- break;
- }
- case 'nextframe': {
- let video = getVideo();
- if(!video.paused) {
- video.pause();
- }
- seekVideo(1 / 30, false);
- break;
- }
- case 'repeat': {
- let video = getVideo();
- let ct = getCurrentTime();
- switch(button.value) {
- case 'start': {
- if(repeatEnd == null) {
- repeatEnd = video.duration;
- }
- repeatStart = ct;
- break;
- }
- case 'end': {
- if(repeatStart == null) {
- repeatStart = 0;
- }
- repeatEnd = ct;
- break;
- }
- case 'clear': {
- repeatStart = null;
- repeatEnd = null;
- break;
- }
- default: {
- break;
- }
- }
- break;
- }
- case 'playback-rate-decr': {
- changePlaybackRate(-1);
- break;
- }
- case 'playback-rate-incr': {
- changePlaybackRate(1);
- break;
- }
- case 'screenshot': {
- let video = getVideo();
- if(video) {
- let canvas = document.createElement('canvas');
- canvas.width = video.videoWidth;
- canvas.height = video.videoHeight;
- let context = canvas.getContext('2d');
- context.drawImage(video, 0, 0, canvas.width, canvas.height);
- canvas.toBlob(b => {
- let a = document.querySelector('#bml-videoctrl-panel .screenshot-dllink');
- a.href = URL.createObjectURL(b);
- let date = new Date();
- let z = n => ('0' + n).slice(-2);
- a.setAttribute('download', `${date.getFullYear()}${z(date.getMonth() + 1)}${z(date.getDate())}_${z(date.getHours())}${z(date.getMinutes())}${z(date.getSeconds())}_${document.title}_${sec2hms(getCurrentTime(GET_TIME_FROM_TEXT_ELEMENT), '')}.png`);
- a.click();
- });
- }
- break;
- }
- case 'toggle-timestamp-editor': {
- let editorContainer = document.querySelector('#bml-videoctrl-panel .timestamp-editor-container');
- if(editorContainer.hidden) {
- editorContainer.hidden = false;
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- editor.focus();
- }else {
- editorContainer.hidden = true;
- }
- break;
- }
- case 'view-mode': {
- switchEditorMode('view');
- timeTable = getTimeTable();
- lastIndex = null;
- updateTimestampControl();
- break;
- }
- case 'edit-mode': {
- switchEditorMode('edit');
- break;
- }
- case 'copy-all': {
- navigator.clipboard.writeText(getEditorContent());
- break;
- }
- case 'insert-timestamp': {
- insertTimestamp(true);
- break;
- }
- case 'timestamp-jump': {
- if(button.getAttribute('disabled') != 'true') {
- let index = parseInt(button.getAttribute('target-index'));
- let video = getVideo();
- if(index < 0) {
- video.currentTime = 0;
- }else {
- video.currentTime = timeTable[index][0];
- }
- scrollEditorContent();
- }
- break;
- }
- case 'timestamp-anchor-offset-viewer': {
- let offset = parseInt(button.value);
- setViewerTimestampAnchorOffset(offset);
- break;
- }
- case 'timestamp-anchor-offset-editor': {
- let offset = parseInt(button.value);
- setEditorTimestampAnchorOffset(offset);
- break;
- }
- case 'toggle-toolbar': {
- toggleToolbar();
- break;
- }
- case 'toggle-embed': {
- toggleEmbed(panel.parentNode.tagName == 'BODY');
- break;
- }
- case 'minimize': {
- let content = document.querySelector('#bml-videoctrl-panel .panel-content');
- content.hidden = !content.hidden;
- break;
- }
- case 'close': {
- clearInterval(intervalId);
- repeatStart = null;
- repeatEnd = null;
- panel.remove();
- break;
- }
- default: {
- break;
- }
- }
- });
- });
- }
- function hms2sec(string) {
- let result = string.match(/(?:(?<h>\d+)\:)?(?<m>[0-5]?\d)\:(?<s>[0-5]\d)(?![\d<])/);
- return parseInt(result.groups.h || 0) * 3600 + parseInt(result.groups.m) * 60 + parseInt(result.groups.s);
- }
- function sec2ft(sec, separator) {
- if(separator == null) {
- separator = ':';
- }
- let z = n => ('0' + n).slice(-2);
- let h = Math.floor(sec / 3600);
- if(APPLY_PADDING_TO_HOUR) h = z(h);
- let m = z(Math.floor((sec / 60) % 60));
- if(!APPLY_PADDING_TO_MIN && HIDE_HOUR_IF_ZERO && parseInt(h) == 0) m = parseInt(m);
- let s = z(Math.floor(sec % 60));
- let ft = (HIDE_HOUR_IF_ZERO && parseInt(h) == 0) ? `${m}${separator}${s}` : `${h}${separator}${m}${separator}${s}`;
- return ft;
- }
- function sec2hms(sec, separator) {
- if(separator == null) {
- separator = ':';
- }
- let z = n => ('0' + n).slice(-2);
- let h = Math.floor(sec / 3600);
- if(APPLY_PADDING_TO_HOUR) h = z(h);
- let m = z(Math.floor((sec / 60) % 60));
- let s = z(Math.floor(sec % 60));
- return `${h}${separator}${m}${separator}${s}`;
- }
- function sec2ms(sec) {
- let z = n => ('0' + n).slice(-2);
- let ms = z(Math.floor((sec % 1) * 100));
- return `${ms}`;
- }
- function getVideo() {
- let videos = Array.from(document.querySelectorAll('video, bwp-video'));
- let video;
- if(videos.length == 1) {
- video = videos[0];
- }else {
- videos.sort((a, b) => {
- const durationA = a.duration || 0;
- const durationB = b.duration || 0;
- return durationB - durationA;
- });
- for(let i = 0; i < videos.length; i++) {
- if(!videos[i].paused) {
- video = videos[i];
- break;
- }
- }
- if(!video) video = videos[0];
- }
- if(!video) {
- let frames = window.frames;
- for(let i = 0; i < frames.length; i++) {
- video = frames[i].document.querySelector('video, bwp-video');
- if(video) {
- parentFrame = frames[i];
- break;
- }
- }
- }
- return video;
- }
- function seekVideo(sec, roundFlag) {
- let video = getVideo();
- if(video) {
- let targetTime = Math.min(video.duration, Math.max(0, getCurrentTime() + sec));
- if(roundFlag) {
- targetTime = (sec > 0 || !video.paused) ? Math.floor(targetTime) : Math.ceil(targetTime);
- }
- video.currentTime = targetTime;
- }
- }
- function changePlaybackRate(delta) {
- let video = getVideo();
- let currentPlaybackRate = video.playbackRate;
- let lowerPlaybackRate = currentPlaybackRate;
- let higherPlaybackRate = currentPlaybackRate;
- for(let i = 0; i < PLAYBACK_RATE.length; i++) {
- if(PLAYBACK_RATE[i] < currentPlaybackRate) {
- lowerPlaybackRate = PLAYBACK_RATE[i];
- }
- if(PLAYBACK_RATE[i] > currentPlaybackRate) {
- higherPlaybackRate = PLAYBACK_RATE[i];
- break;
- }
- }
- video.playbackRate = (delta == 1) ? higherPlaybackRate : (delta == -1) ? lowerPlaybackRate : (delta == 0) ? 1 : currentPlaybackRate;
- }
- function getCurrentTime(flag) {
- let video = getVideo();
- if(flag) {
- let time = document.querySelector(TIME_SELECTOR.join(','));
- let r = time && time.textContent.match(/(?:(\d+)\:)?([0-5]?\d)\:([0-5]\d)/);
- let ct = r && ((parseInt(r[1]) || 0) * 3600 + parseInt(r[2]) * 60 + parseInt(r[3]));
- if(ct) {
- currentTimeOffset = ct - (video.currentTime - 0.5);
- if(Math.abs(currentTimeOffset) > 3) {
- return ct;
- }else {
- return video.currentTime;
- }
- }else {
- if(currentTimeOffset && Math.abs(currentTimeOffset) > 3) {
- return video.currentTime + currentTimeOffset;
- }else {
- return video.currentTime;
- }
- }
- }else {
- return video.currentTime;
- }
- }
- function getTimeFromString(string) {
- if(!string) return;
- let regexp, targetTime;
- let sign = 0;
- regexp = string.trim().match(/^\+/);
- if(regexp) sign = 1;
- regexp = string.trim().match(/^-/);
- if(regexp) sign = -1;
- /* secounds */
- regexp = string.trim().match(/^[-+]?\d+(?:\.\d+)?$/);
- if(regexp) targetTime = parseFloat(regexp[0]);
- /* [hh:]mm:ss */
- regexp = string.match(/(?:(\d+)\:)?([0-5]?\d)\:([0-5]\d)/);
- if(regexp) targetTime = (parseInt(regexp[1]) || 0) * 3600 + parseInt(regexp[2]) * 60 + parseInt(regexp[3]);
- /* delta */
- if(targetTime && sign != 0) {
- targetTime = getCurrentTime() + targetTime * sign;
- }
- return targetTime;
- }
- function getTimeTable() {
- let timestamp = document.querySelector('#bml-videoctrl-panel .timestamp-viewer .content');
- let anchors = Array.from(timestamp.querySelectorAll('.timestamp-anker[time]'));
- let timeTable = [];
- anchors.forEach(anchor => {
- let index = -1;
- for(let i = 0; i < timeTable.length; i++) {
- if(timeTable[i][0] == parseInt(anchor.getAttribute('time'))) {
- index = i;
- break;
- }
- }
- let timeItem = [];
- if(index == -1) {
- timeItem.push(parseInt(anchor.getAttribute('time')));
- let regex = new RegExp('^.*' + anchor.textContent + '.*$', 'mu');
- timeItem.push(timestamp.textContent.match(regex)[0]);
- timeItem.push(anchor.textContent);
- timeTable.push(timeItem);
- }
- });
- timeTable.sort((a, b) => {
- return a[0] - b[0];
- });
- return timeTable;
- }
- function getTimeTableIndex(time) {
- if(!timeTable || timeTable.length == 0) return -1;
- let index;
- if(time < timeTable[0][0]) {
- index = -1;
- }else if(time >= timeTable[timeTable.length - 1][0]) {
- index = timeTable.length - 1;
- }else {
- for(let i = 1; i < timeTable.length; i++) {
- if(timeTable[i][0] > time) {
- index = i - 1;
- break;
- }
- }
- }
- return index;
- }
- function toggleEmbed(boolean) {
- let video = getVideo();
- let parent;
- let content = document.querySelector('#bml-videoctrl-panel .timestamp-editor-container[mode="edit"] .timestamp-editor .content, #bml-videoctrl-panel .timestamp-editor-container[mode="view"] .timestamp-viewer .content');
- let scrollTop = content.scrollTop;
- if(!parentFrame && boolean) {
- parent = document.querySelector(PARENT_SELECTOR.join(','));
- if(!parent) {
- parent = video.parentNode.parentNode;
- }
- }else {
- parent = document.body;
- }
- parent.appendChild(panel);
- panel.setAttribute('embedded', !parentFrame && boolean);
- content = document.querySelector('#bml-videoctrl-panel .timestamp-editor-container[mode="edit"] .timestamp-editor .content, #bml-videoctrl-panel .timestamp-editor-container[mode="view"] .timestamp-viewer .content');
- content.scrollTop = scrollTop;
- }
- function toggleToolbar(boolean) {
- let toolbar = document.querySelector('#bml-videoctrl-panel .toolbar-container');
- if(boolean == null) {
- toolbar.hidden = !toolbar.hidden;
- }else {
- toolbar.hidden = boolean;
- }
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- editor.focus();
- }
- function getEditorContent() {
- let content = document.querySelector('#bml-videoctrl-panel .timestamp-editor-container[mode="edit"] .timestamp-editor .content, #bml-videoctrl-panel .timestamp-editor-container[mode="view"] .timestamp-viewer .content');
- return content.textContent || content.value;
- }
- function editor2viewer() {
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- let viewer = document.querySelector('#bml-videoctrl-panel .timestamp-viewer .content');
- let innerHTML = editor.value.replace(/(?:(\d+)\:)?([0-5]?\d)\:([0-5]\d)(?![\d<])/g, ($0, h, m, s) => {
- let time = parseInt(h || 0) * 3600 + parseInt(m) * 60 + parseInt(s);
- return `<a class="timestamp-anker" href="#" dir="auto" time="${time}">${$0}</a>`;
- });
- let regexp = new RegExp(`[^\n]+`, 'g');
- innerHTML = innerHTML.replace(regexp, '<span class="content-row">$&</span>');
- viewer.innerHTML = innerHTML;
- }
- function viewer2editor() {
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- let viewer = document.querySelector('#bml-videoctrl-panel .timestamp-viewer .content');
- editor.value = viewer.textContent;
- }
- function switchEditorMode(mode) {
- let editorContainer = document.querySelector('#bml-videoctrl-panel .timestamp-editor-container');
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- let viewer = document.querySelector('#bml-videoctrl-panel .timestamp-viewer .content');
- if(editorContainer.getAttribute('mode') != mode) {
- switch(mode) {
- case 'edit': {
- let scrollTop = viewer.scrollTop;
- viewer2editor();
- editorContainer.setAttribute('mode', 'edit');
- editor.scrollTop = scrollTop;
- break;
- }
- case 'view': {
- let scrollTop = editor.scrollTop;
- editor2viewer();
- editorContainer.setAttribute('mode', 'view');
- viewer.scrollTop = scrollTop;
- break;
- }
- }
- let video = getVideo();
- video.focus();
- }
- }
- function scrollEditorContent(){
- let content = document.querySelector('#bml-videoctrl-panel .timestamp-viewer .content:not(:hover)');
- if(content) {
- let video = getVideo();
- let index = getTimeTableIndex(video.currentTime);
- if(index < 0) {
- content.scrollTop = 0;
- }else {
- let contentRect = content.getBoundingClientRect();
- let CurrentRow = content.querySelector(`.content-row.current`);
- if(CurrentRow) {
- let CurrentRowRect = CurrentRow.getBoundingClientRect();
- if((CurrentRowRect.top - contentRect.top < 0) || (contentRect.bottom - CurrentRowRect.bottom < 0)) {
- content.scrollTop = content.scrollTop + CurrentRowRect.top - contentRect.top - (contentRect.height / 2);
- }
- }
- }
- }
- }
- function copyTimestamp() {
- let ct = Math.floor(getCurrentTime(GET_TIME_FROM_TEXT_ELEMENT));
- let ft = sec2ft(ct);
- if(COPY_WITH_SPACE) ft += ' ';
- navigator.clipboard.writeText(ft);
- return ft;
- }
- function insertTimestamp(insertEnd) {
- let ct = Math.floor(getCurrentTime(GET_TIME_FROM_TEXT_ELEMENT));
- let ft = sec2ft(ct) + ' ';
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- editor.focus();
- if(insertEnd) {
- let insertNode = (editor.value) ? '\n' + ft : ft;
- editor.setSelectionRange(editor.value.length, editor.value.length);
- document.execCommand('insertText', false, insertNode);
- editor.scrollTop = editor.scrollHeight;
- }else {
- let value = editor.value;
- let posStart = editor.selectionStart;
- let posEnd = editor.selectionEnd;
- let range = value.slice(posStart, posEnd);
- let gap = range.length;
- let beforeNode = value.slice(0, posStart);
- let afterNode = value.slice(posEnd);
- let insertNode = ft;
- gap = insertNode.length - gap;
- document.execCommand('insertText', false, insertNode);
- editor.setSelectionRange(posEnd + gap, posEnd + gap);
- }
- }
- function setViewerTimestampAnchorOffset(offset) {
- backupTrigger = true;
- let anchor = document.querySelector('#bml-videoctrl-panel .timestamp-viewer .timestamp-anker.current');
- if(!anchor) return;
- let time = Math.max(0, hms2sec(anchor.textContent) + offset);
- anchor.textContent = sec2ft(time);
- anchor.setAttribute('time', time);
- let video = getVideo();
- video.currentTime = time;
- timeTable = getTimeTable();
- lastIndex = null;
- updateTimestampControl();
- }
- function setEditorTimestampAnchorOffset(offset) {
- backupTrigger = true;
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- editor.focus();
- let value = editor.value;
- let posStart = editor.selectionStart;
- let posEnd = editor.selectionEnd;
- let posDirection = editor.selectionDirection;
- if(posStart == posEnd) {
- posStart = value.slice(0, posStart).match(/[0-9:\-]*$/).index;
- posEnd = posStart + value.slice(posStart).match(/[0-9:\-]*/)[0].length;
- editor.setSelectionRange(posStart, posEnd);
- }
- let range = value.slice(posStart, posEnd);
- let gap = range.length;
- let beforeNode = value.slice(0, posStart);
- let afterNode = value.slice(posEnd);
- let insertNode = range.replace(/(-?)(?:(\d+)\:)?([0-5]?\d)\:([0-5]\d)(?!\d|\:)/g, ($0, minus, h, m, s) => {
- let sign, time, ft;
- sign = (minus) ? -1 : 1;
- time = sign * (parseInt(h || 0) * 3600 + parseInt(m) * 60 + parseInt(s)) + offset;
- sign = Math.sign(time);
- ft = (sign == -1) ? '-' : '';
- ft += sec2ft(Math.abs(time));
- return ft;
- });
- gap = insertNode.length - gap;
- document.execCommand('insertText', false, insertNode);
- editor.setSelectionRange(posStart, posEnd + gap, posDirection);
- }
- function getBackup() {
- return JSON.parse(localStorage.getItem('bml-videoctrl-backup'));
- }
- function saveBackup() {
- backupTrigger = false;
- let backup = getBackup() || [];
- for(let i = 0; i < backup.length; i++) {
- if(backup[i].url == document.URL) {
- backup.splice(i, 1);
- }
- }
- backup.push({'url': document.URL, 'content': getEditorContent()});
- if(backup.length > 10) {
- backup.shift();
- }
- localStorage.setItem('bml-videoctrl-backup', JSON.stringify(backup));
- }
- function loadBackup() {
- let backup = getBackup() || [];
- let editor = document.querySelector('#bml-videoctrl-panel .timestamp-editor .content');
- editor.value = '';
- for(let i = 0; i < backup.length; i++) {
- if(backup[i].url == document.URL) {
- editor.value = backup[i].content;
- break;
- }
- }
- editor2viewer();
- }
- function updateTimeDisplay() {
- let ct = getCurrentTime(GET_TIME_FROM_TEXT_ELEMENT);
- let beforePoint = document.querySelector('#bml-videoctrl-panel .text-before-point');
- let afterPoint = document.querySelector('#bml-videoctrl-panel .text-after-point');
- beforePoint.textContent = sec2ft(ct);
- afterPoint.textContent = sec2ms(ct);
- let playbackRate = document.querySelector('#bml-videoctrl-panel .current-playback-rate');
- let video = getVideo();
- if(video.playbackRate == 1) {
- playbackRate.textContent = '';
- }else {
- playbackRate.textContent = `x${video.playbackRate.toFixed(2)}`;
- }
- }
- function updateTimestampControl() {
- if(!timeTable) return;
- let video = getVideo();
- let index = getTimeTableIndex(video.currentTime);
- if(lastIndex != null && lastIndex == index) return;
- lastIndex = index;
- /* 前のタイムスタンプボタン */
- let prev = document.querySelector('#bml-videoctrl-panel .timestamp-prev');
- if(index == -1) {
- prev.setAttribute('disabled', true);
- }else {
- prev.removeAttribute('disabled');
- }
- prev.title = (index == -1) ? '' : (index == 0) ? '00:00 Start' : timeTable[index - 1][1];
- prev.setAttribute('target-index', index - 1);
- /* 現在のタイムスタンプボタン */
- let replay = document.querySelector('#bml-videoctrl-panel .timestamp-replay');
- replay.title = (index == -1) ? '00:00 Start' : timeTable[index][1];
- replay.setAttribute('target-index', index);
- /* 次のタイムスタンプボタン */
- let next = document.querySelector('#bml-videoctrl-panel .timestamp-next');
- if(index == timeTable.length - 1) {
- next.setAttribute('disabled', true);
- }else {
- next.removeAttribute('disabled');
- }
- next.title = (index < timeTable.length - 1) ? timeTable[index + 1][1] : next.title = '';
- next.setAttribute('target-index', index + 1);
- /* 現在のタイムスタンプを強調 */
- let currentRows = Array.from(document.querySelectorAll('#bml-videoctrl-panel .timestamp-viewer .content-row.current'));
- currentRows.forEach(currentRow => {
- currentRow.classList.remove('current');
- });
- if(index != -1) {
- let anchors = Array.from(document.querySelectorAll('#bml-videoctrl-panel .timestamp-viewer .timestamp-anker[time]'));
- anchors.forEach(anchor => {
- if(anchor.textContent == timeTable[index][2]) {
- anchor.classList.add('current');
- anchor.parentNode.classList.add('current');
- }else {
- anchor.classList.remove('current');
- }
- });
- }else {
- let anchors = Array.from(document.querySelectorAll('#bml-videoctrl-panel .timestamp-viewer .timestamp-anker[time].current'));
- anchors.forEach(anchor => {
- anchor.classList.remove('current');
- });
- }
- scrollEditorContent();
- }
- function checkRepeat() {
- let toolbar = document.querySelector('#bml-videoctrl-panel .repeat-toolbar');
- let text = document.querySelector('#bml-videoctrl-panel .repeat-toolbar .repeat-text');
- if(repeatStart !== null && repeatEnd !== null) {
- toolbar.hidden = false;
- text.textContent = `repeat: ${sec2ft(repeatStart)}.${sec2ms(repeatStart)} - ${sec2ft(repeatEnd)}.${sec2ms(repeatEnd)}`;
- let video = getVideo();
- if(video) {
- let ct = getCurrentTime();
- if(ct < repeatStart || ct > repeatEnd) {
- video.currentTime = repeatStart;
- }
- }
- }else {
- toolbar.hidden = true;
- }
- }
- function timeupdate() {
- let panel = document.querySelector('#bml-videoctrl-panel');
- if(!panel) {
- clearInterval(intervalId);
- return;
- }
- if(currentURL != document.URL) {
- currentURL = document.URL;
- loadBackup();
- }
- updateTimeDisplay();
- updateTimestampControl();
- checkRepeat();
- if(backupTrigger) {
- saveBackup();
- }
- }
- })());
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement