From 1a3ebdd3dddc05ba0670dc4d81bc0d5a48ae3faa Mon Sep 17 00:00:00 2001 From: Andrew S Date: Tue, 21 Nov 2023 16:42:34 -0600 Subject: [PATCH 01/20] Youtube improvements --- chrome/player/FastStreamClient.mjs | 10 ++++++++-- chrome/player/players/dash/DashPlayer.mjs | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/chrome/player/FastStreamClient.mjs b/chrome/player/FastStreamClient.mjs index 3a6bcc23..13344a3b 100644 --- a/chrome/player/FastStreamClient.mjs +++ b/chrome/player/FastStreamClient.mjs @@ -14,6 +14,7 @@ import {DOMElements} from './ui/DOMElements.mjs'; import {AudioConfigManager} from './ui/audio/AudioConfigManager.mjs'; import {EnvUtils} from './utils/EnvUtils.mjs'; import {Localize} from './modules/Localize.mjs'; +import {PlayerModes} from './enums/PlayerModes.mjs'; export class FastStreamClient extends EventEmitter { @@ -302,6 +303,11 @@ export class FastStreamClient extends EventEmitter { async setSource(source) { source = source.copy(); + let autoPlay = this.options.autoPlay; + if (source.mode === PlayerModes.ACCELERATED_YT) { + autoPlay = true; + } + console.log('setSource', source); await this.resetPlayer(); this.source = source; @@ -316,7 +322,7 @@ export class FastStreamClient extends EventEmitter { await this.player.setSource(source); this.interfaceController.addVideo(this.player.getVideo()); - if (this.options.autoPlay) { + if (autoPlay) { this.player.getVideo().autoplay = true; } @@ -353,7 +359,7 @@ export class FastStreamClient extends EventEmitter { this.updateCSSFilters(); this.interfaceController.updateToolVisibility(); - if (this.options.autoPlay) { + if (autoPlay) { this.play(); } } diff --git a/chrome/player/players/dash/DashPlayer.mjs b/chrome/player/players/dash/DashPlayer.mjs index dbaa9412..f12596f3 100644 --- a/chrome/player/players/dash/DashPlayer.mjs +++ b/chrome/player/players/dash/DashPlayer.mjs @@ -35,6 +35,7 @@ export default class DashPlayer extends EventEmitter { bufferToKeep: 10, bufferTimeAtTopQuality: 10, bufferTimeAtTopQualityLongForm: 10, + bufferPruningInterval: 1, }, }, From 4c23d7385f931b6eea1a904ad5b6e944f3932474 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Tue, 21 Nov 2023 18:38:51 -0600 Subject: [PATCH 02/20] Support theatre mode --- chrome/content.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/chrome/content.js b/chrome/content.js index 8f1fffa4..ca83c851 100644 --- a/chrome/content.js +++ b/chrome/content.js @@ -273,7 +273,7 @@ function getParentElementsWithSameBounds(element) { async function getVideo() { if (is_url_yt(window.location.href)) { - const ytplayer = document.querySelectorAll('body #player')[0]; + const ytplayer = get_yt_video_element(); if (ytplayer) { const visibleRatio = await isVisible(ytplayer); const rect = ytplayer.getBoundingClientRect(); @@ -353,9 +353,29 @@ function is_url_yt_embed(urlStr) { return pathname.startsWith('/embed'); } +// eslint-disable-next-line camelcase +function get_yt_video_element() { + const queries = [ + 'body #player', + 'body #player-full-bleed-container', + ]; + for (let i = 0; i < queries.length; i++) { + const ytplayer = document.querySelectorAll(queries[i])[0]; + if (ytplayer) { + const style = window.getComputedStyle(ytplayer); + if (style.display === 'none') { + continue; + } + + return ytplayer; + } + } + return null; +} + if (is_url_yt(window.location.href)) { const observer = new MutationObserver((mutations)=> { - const pnode = document.querySelectorAll('body #player')[0]; + const pnode = get_yt_video_element(); const isWatch = is_url_yt_watch(window.location.href); const isEmbed = is_url_yt_embed(window.location.href); if (pnode && (isWatch || isEmbed)) { From 2e34793c85e95ca33cc9ae45613aa93d61af3f4e Mon Sep 17 00:00:00 2001 From: Andrew S Date: Tue, 21 Nov 2023 18:46:36 -0600 Subject: [PATCH 03/20] Support theatre mode --- chrome/content.js | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/chrome/content.js b/chrome/content.js index ca83c851..67cc9fce 100644 --- a/chrome/content.js +++ b/chrome/content.js @@ -273,7 +273,7 @@ function getParentElementsWithSameBounds(element) { async function getVideo() { if (is_url_yt(window.location.href)) { - const ytplayer = get_yt_video_element(); + const ytplayer = lastPlayerNode; if (ytplayer) { const visibleRatio = await isVisible(ytplayer); const rect = ytplayer.getBoundingClientRect(); @@ -354,39 +354,43 @@ function is_url_yt_embed(urlStr) { } // eslint-disable-next-line camelcase -function get_yt_video_element() { +function get_yt_video_elements() { const queries = [ 'body #player', 'body #player-full-bleed-container', ]; + const elements = []; for (let i = 0; i < queries.length; i++) { - const ytplayer = document.querySelectorAll(queries[i])[0]; - if (ytplayer) { - const style = window.getComputedStyle(ytplayer); - if (style.display === 'none') { - continue; - } - - return ytplayer; + const ytplayer = document.querySelectorAll(queries[i]); + for (let j = 0; j < ytplayer.length; j++) { + elements.push(ytplayer[j]); } } - return null; + return elements; } if (is_url_yt(window.location.href)) { const observer = new MutationObserver((mutations)=> { - const pnode = get_yt_video_element(); const isWatch = is_url_yt_watch(window.location.href); const isEmbed = is_url_yt_embed(window.location.href); - if (pnode && (isWatch || isEmbed)) { - const rect = pnode.getBoundingClientRect(); - if ((isEmbed || rect.x !== 0) && rect.width * rect.height > 0 && lastPlayerNode !== pnode) { - lastPlayerNode = pnode; - chrome.runtime.sendMessage({ - type: 'yt_loaded', - url: window.location.href, - }); + if (isWatch || isEmbed) { + const pnodes = get_yt_video_elements(); + if (pnodes.length === 0) { + return; } + + pnodes.find((pnode) => { + const rect = pnode.getBoundingClientRect(); + if ((isEmbed || rect.x !== 0 || pnode.id !== 'player') &&rect.width * rect.height > 0 && lastPlayerNode !== pnode) { + lastPlayerNode = pnode; + chrome.runtime.sendMessage({ + type: 'yt_loaded', + url: window.location.href, + }); + return true; + } + return false; + }); } }); observer.observe(document, {attributes: false, childList: true, characterData: false, subtree: true}); From 8abaca5fa875ea5ef335beb3bf85ac5ba07226c4 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Tue, 21 Nov 2023 18:48:29 -0600 Subject: [PATCH 04/20] Cleanup --- chrome/content.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/chrome/content.js b/chrome/content.js index 67cc9fce..cc54b635 100644 --- a/chrome/content.js +++ b/chrome/content.js @@ -275,12 +275,6 @@ async function getVideo() { if (is_url_yt(window.location.href)) { const ytplayer = lastPlayerNode; if (ytplayer) { - const visibleRatio = await isVisible(ytplayer); - const rect = ytplayer.getBoundingClientRect(); - if (rect.width * rect.height * visibleRatio < 1000) { - return null; - } - return { size: ytplayer.clientWidth * ytplayer.clientHeight, highest: ytplayer, @@ -374,15 +368,15 @@ if (is_url_yt(window.location.href)) { const isWatch = is_url_yt_watch(window.location.href); const isEmbed = is_url_yt_embed(window.location.href); if (isWatch || isEmbed) { - const pnodes = get_yt_video_elements(); - if (pnodes.length === 0) { + const playerNodes = get_yt_video_elements(); + if (playerNodes.length === 0) { return; } - pnodes.find((pnode) => { - const rect = pnode.getBoundingClientRect(); - if ((isEmbed || rect.x !== 0 || pnode.id !== 'player') &&rect.width * rect.height > 0 && lastPlayerNode !== pnode) { - lastPlayerNode = pnode; + playerNodes.find((playerNode) => { + const rect = playerNode.getBoundingClientRect(); + if ((isEmbed || rect.x !== 0 || playerNode.id !== 'player') && rect.width * rect.height > 0 && lastPlayerNode !== playerNode) { + lastPlayerNode = playerNode; chrome.runtime.sendMessage({ type: 'yt_loaded', url: window.location.href, From 246ed2cf7f2b0fe3a42b084ab6e62cefa58e4ec4 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Tue, 21 Nov 2023 18:58:19 -0600 Subject: [PATCH 05/20] Refactor player discovery --- chrome/content.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/chrome/content.js b/chrome/content.js index cc54b635..04139112 100644 --- a/chrome/content.js +++ b/chrome/content.js @@ -1,4 +1,4 @@ -let lastPlayerNode = null; +const YTPlayerNodesList = []; const iframeMap = new Map(); const players = []; window.addEventListener('message', (e) => { @@ -111,7 +111,7 @@ chrome.runtime.onMessage.addListener( player.iframe.parentElement.replaceChild(player.old, player.iframe); }); players.length = 0; - lastPlayerNode = null; + YTPlayerNodesList.length = 0; sendResponse('ok'); } else if (request.type === 'get_video_size') { getVideo().then((video) => { @@ -273,7 +273,12 @@ function getParentElementsWithSameBounds(element) { async function getVideo() { if (is_url_yt(window.location.href)) { - const ytplayer = lastPlayerNode; + const ytplayer = YTPlayerNodesList.reduceRight((result, current) => { + const resultSize = result ? result.clientWidth * result.clientHeight : 0; + const currentSize = current.clientWidth * current.clientHeight; + return (currentSize > resultSize) ? current : result; + }, null); + if (ytplayer) { return { size: ytplayer.clientWidth * ytplayer.clientHeight, @@ -375,8 +380,8 @@ if (is_url_yt(window.location.href)) { playerNodes.find((playerNode) => { const rect = playerNode.getBoundingClientRect(); - if ((isEmbed || rect.x !== 0 || playerNode.id !== 'player') && rect.width * rect.height > 0 && lastPlayerNode !== playerNode) { - lastPlayerNode = playerNode; + if ((isEmbed || rect.x !== 0 || playerNode.id !== 'player') && rect.width * rect.height > 0 && !YTPlayerNodesList.includes(playerNode)) { + YTPlayerNodesList.push(playerNode); chrome.runtime.sendMessage({ type: 'yt_loaded', url: window.location.href, From c582f439df460688ca5098a8ded9afd94e48e9eb Mon Sep 17 00:00:00 2001 From: Andrew S Date: Tue, 21 Nov 2023 19:37:45 -0600 Subject: [PATCH 06/20] Autoplay doesn't work so just remove it for now --- chrome/player/FastStreamClient.mjs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/chrome/player/FastStreamClient.mjs b/chrome/player/FastStreamClient.mjs index 13344a3b..d05a5bb1 100644 --- a/chrome/player/FastStreamClient.mjs +++ b/chrome/player/FastStreamClient.mjs @@ -14,7 +14,6 @@ import {DOMElements} from './ui/DOMElements.mjs'; import {AudioConfigManager} from './ui/audio/AudioConfigManager.mjs'; import {EnvUtils} from './utils/EnvUtils.mjs'; import {Localize} from './modules/Localize.mjs'; -import {PlayerModes} from './enums/PlayerModes.mjs'; export class FastStreamClient extends EventEmitter { @@ -303,10 +302,7 @@ export class FastStreamClient extends EventEmitter { async setSource(source) { source = source.copy(); - let autoPlay = this.options.autoPlay; - if (source.mode === PlayerModes.ACCELERATED_YT) { - autoPlay = true; - } + const autoPlay = this.options.autoPlay; console.log('setSource', source); await this.resetPlayer(); From 2e17b0e772ffe2bb113c68a40b7aa85b6d09fa4b Mon Sep 17 00:00:00 2001 From: "Re*Index. (ot_inc)" <32851879+reindex-ot@users.noreply.github.com> Date: Wed, 22 Nov 2023 21:56:31 +0900 Subject: [PATCH 07/20] Translated Japanese (#20) --- chrome/_locales/ja/messages.json | 714 +++++++++++++++++++++++++++++++ 1 file changed, 714 insertions(+) create mode 100644 chrome/_locales/ja/messages.json diff --git a/chrome/_locales/ja/messages.json b/chrome/_locales/ja/messages.json new file mode 100644 index 00000000..869d23ef --- /dev/null +++ b/chrome/_locales/ja/messages.json @@ -0,0 +1,714 @@ +{ + "extension_name": { + "message": "FastStream Video Player" + }, + "extension_description": { + "message": "Stream without buffering, a great video player and download accelerator all in one." + }, + "extension_toggle_label": { + "message": "FastStream に切り替える" + }, + "welcome_page_title": { + "message": "FastStream へようこそ" + }, + "welcome_page_usage_header": { + "message": "使用方法" + }, + "welcome_page_usage_content0": { + "message": "FastStream の使用方法は 4 つあります" + }, + "welcome_page_usage_content1": { + "message": "動画のある Web サイトを開いてアイコンをクリックで FastStream を ON にする事ができます。動画を再生するとクライアントは自動的に FastStream に置き換わります。" + }, + "welcome_page_usage_content2": { + "message": "新しいタブを開いて拡張機能のアイコンをクリックでプレーヤーを開く事ができます。" + }, + "welcome_page_usage_content3": { + "message": "オプションを有効化している場合に、mp4/mpd/m3u8 の URL にアクセスする事でプレーヤーが再生をします。" + }, + "welcome_page_usage_content4": { + "message": "新しいタブでプレーヤーを開いておく事で、他の場所を参照してソースを取得します。" + }, + "welcome_page_usage_end": { + "message": "FastStream をインストールしていただき感謝します! 問題やご提案がありましたらお知らせください。" + }, + "welcome_page_keybinds_header": { + "message": "デフォルトのキーボードコントロール" + }, + "welcome_page_keybinds_content0": { + "message": "これらのキーバインドは拡張機能の設定で変更できます" + }, + "welcome_page_keybinds_content1": { + "message": "矢印キー - 前後の 1 秒間隔のシーク" + }, + "welcome_page_keybinds_content2": { + "message": "キー - 前後の 10 秒間隔のシーク" + }, + "welcome_page_keybinds_content3": { + "message": "キー - シークを戻す" + }, + "welcome_page_keybinds_content4": { + "message": "矢印キー - 10% の音量を上下" + }, + "welcome_page_keybinds_content5": { + "message": "- フルスクリーン" + }, + "welcome_page_keybinds_content6": { + "message": "- 同時リクエストの増減" + }, + "welcome_page_keybinds_content7": { + "message": "- 強制再試行キー - これを使用する事で失敗をしたダウンロードを再試行します" + }, + "welcome_page_keybinds_content8": { + "message": "- 該当時にイントロ/アウトロをスキップ" + }, + "welcome_page_keybinds_content9": { + "message": "- プレーヤーを表示/非表示" + }, + "perms_page_title": { + "message": "FastStream の権限" + }, + "perms_page_about_header": { + "message": "FastStream に必要な権限について" + }, + "perms_page_about_content0": { + "message": "拡張機能を動作させるには特定の権限を許可する必要があります。これらの権限は基本的な機能のみで使用されます。" + }, + "perms_page_about_content1": { + "message": "FastStream はユーザーからデータの収集、追跡、サーバーにデータの送信は一切しません。データを送信するサーバーもありません。" + }, + "perms_page_about_content2": { + "message": "信用ができませんか? FastStream のソースは次の場所で入手ができます。" + }, + "perms_page_about_content3": { + "message": "自分でコードを確認する事もできますし、指示に従って自分だけのカスタムビルドを手動でインストールする事もできます。" + }, + "perms_page_breakdown_header": { + "message": "権限の内訳" + }, + "perms_page_breakdown_content0": { + "message": "FastStream が必要とする権限とその理由を説明します。" + }, + "perms_page_breakdown_content1": { + "message": "注: リンクされた例では古いバージョンのコードが参照されている場合がありますが、要点は全体的に同じであるはずです。リストはすべてを網羅している訳ではなく、項目の重要度の順で並べられています。" + }, + "perms_page_breakdown_perm1h": { + "message": "すべての Web サイトにアクセスをして変更できる事" + }, + "perms_page_breakdown_perm1d": { + "message": "FastStream はプレーヤーを Web サイトに直接挿入ができるようにするため、すべての Web サイトにアクセスできるようにする必要があります。次の目的でアクセスをした Web サイトにコンテンツスクリプトを挿入します:" + }, + "perms_page_breakdown_perm1r1": { + "message": "プレーヤーの置き換え、表示されている最大の動画の要素を特定。" + }, + "perms_page_breakdown_perm1r2": { + "message": "プレーヤーがフレームに挿入されるときに全画面の権限を有効化。" + }, + "perms_page_breakdown_perm1r3": { + "message": "字幕の 要素をページから削除。" + }, + "perms_page_breakdown_perm2h": { + "message": "webRequest API にアクセス" + }, + "perms_page_breakdown_perm2d": { + "message": "FastStream は動画ソースへの HTTP リクエストをインターセプトを可能にする必要があります:" + }, + "perms_page_breakdown_perm2r1": { + "message": "動画のソースを特定" + }, + + "perms_page_breakdown_perm3h": { + "message": "declarativeNetRequest API へのアクセス" + }, + "perms_page_breakdown_perm3d": { + "message": "FastStream は選択されたリクエストの HTTP ヘッダーを変更できるようにする必要があります:" + }, + "perms_page_breakdown_perm3r1": { + "message": "動画ソースのヘッダーを上書き。" + }, + "perms_page_breakdown_perm4h": { + "message": "ブラウザー上にデータを保存" + }, + "perms_page_breakdown_perm4d": { + "message": "FastStream は、ユーザーの設定を記憶できるようにするためブラウザーにデータを保存可能にする必要があります。" + }, + "perms_page_breakdown_perm4r1": { + "message": "拡張機能の一般オプション。" + }, + "perms_page_breakdown_perm5h": { + "message": "Tabs API にアクセス" + }, + "perms_page_breakdown_perm5d": { + "message": "FastStream は、各タブ上のプレーヤーとコンテンツスクリプト間でメッセージを送信可能にする必要があります。" + }, + "perms_page_breakdown_perm5r1": { + "message": "バックグラウンドスクリプトからプレーヤーにソースを送信" + }, + "perms_page_granted": { + "message": "許可" + }, + "perms_page_notgranted": { + "message": "許可がされていません、クリックで許可をします" + }, + "player_skipintro": { + "message": "イントロをスキップ" + }, + "player_skipoutro": { + "message": "アウトロをスキップ" + }, + "player_hidecontrols": { + "message": "コントロールを隠す" + }, + "player_loading": { + "message": "読み込み中..." + }, + "player_nosource_header": { + "message": "ソースは読み込まれていません!" + }, + "player_nosource_instruction1": { + "message": "動画ファイルをドラッグ&ドロップで再生できます。" + }, + "player_nosource_instruction2p1": { + "message": "クリック" + }, + "player_nosource_instruction2p2": { + "message": "他のタブから検出されたソースを読み込み。" + }, + "player_playpause_label": { + "message": "動画を再生/一時停止" + }, + "player_mute_label": { + "message": "オーディオを消音" + }, + "player_timestamp_label": { + "message": "タイムスタンプ" + }, + "player_sourcesbrowser_title": { + "message": "ソースの参照" + }, + "player_sourcesbrowser_toggle_label": { + "message": "ソースの参照を開く" + }, + "player_sourcesbrowser_closebtn_label": { + "message": "ソースの参照を閉じる" + }, + "player_audioconfig_title": { + "message": "オーディオの設定" + }, + "player_audioconfig_toggle_label": { + "message": "オーディオ設定ウィンドウを開く" + }, + "player_audioconfig_toggle_closebtn_label": { + "message": "オーディオ設定ウィンドウを閉じる" + }, + "player_opensubtitles_title": { + "message": "OpenSubtitles を検索" + }, + "player_opensubtitles_closebtn_label": { + "message": "OpenSubtitles 検索ウィンドウを閉じる" + }, + "player_opensubtitles_search_placeholder": { + "message": "タイトル、ファイル名などで検索..." + }, + "player_opensubtitles_searchbtn": { + "message": "検索" + }, + "player_opensubtitles_seasonnum": { + "message": "シーズン #" + }, + "player_opensubtitles_episodenum": { + "message": "エピソード #" + }, + "player_opensubtitles_type_all": { + "message": "すべて" + }, + "player_opensubtitles_type_movie": { + "message": "映画" + }, + "player_opensubtitles_type_episode": { + "message": "エピソード" + }, + "player_opensubtitles_language": { + "message": "言語" + }, + "player_opensubtitles_year": { + "message": "年号" + }, + "player_opensubtitles_sortby": { + "message": "ソート順" + }, + "player_opensubtitles_sortby_downloads": { + "message": "ダウンロード" + }, + "player_opensubtitles_sortby_date": { + "message": "アップロード日時" + }, + "player_opensubtitles_sortby_rating": { + "message": "評価" + }, + "player_opensubtitles_sortby_votes": { + "message": "投票" + }, + "player_opensubtitles_sort": { + "message": "ソート" + }, + "player_opensubtitles_sort_desc": { + "message": "下り順" + }, + "player_opensubtitles_sort_asc": { + "message": "上り順" + }, + "player_opensubtitles_searching": { + "message": "検索中..." + }, + "player_opensubtitles_error": { + "message": "エラー: $1" + }, + "player_opensubtitles_disabled": { + "message": "適切なヘッダーを設定できません! この機能を使用するには拡張機能をインストールしてください!" + }, + "player_opensubtitles_error_down": { + "message": "OpenSubtitles はダウンしています!" + }, + "player_opensubtitles_down_alert": { + "message": "OpenSubtitles のダウンロードに失敗しました! サーバーがダウンしているかもしれません!" + }, + "player_opensubtitles_noresults": { + "message": "結果が見つかりません!" + }, + "player_opensubtitles_quota": { + "message": "OpenSubtitles は字幕のダウンロードを制限します! ダウンロードの残り回数がありません! クォータは、 $1 でリセットされます。" + }, + "player_opensubtitles_askopen": { + "message": "OpenSubtitles の Web サイトを開いて字幕ファイルを手動でダウンロードしますか?" + }, + "player_subtitlesmenu_toggle_label": { + "message": "字幕メニューを開く" + }, + "player_subtitlesmenu_backbtn": { + "message": "戻る" + }, + "player_subtitlesmenu_settings": { + "message": "字幕の設定" + }, + "player_subtitlesmenu_testbtn": { + "message": "字幕をテスト" + }, + "player_subtitlesmenu_testbtn_stop": { + "message": "テストを停止" + }, + "player_subtitlesmenu_uploadbtn": { + "message": "ファイルをアップロード" + }, + "player_subtitlesmenu_urlbtn": { + "message": "URL から" + }, + "player_subtitlesmenu_urlprompt": { + "message": "URL を入力" + }, + "player_subtitlesmenu_searchbtn": { + "message": "OpenSubtitles を検索" + }, + "player_subtitlesmenu_clearbtn": { + "message": "字幕をクリア" + }, + "player_subtitlesmenu_settingsbtn": { + "message": "字幕の設定" + }, + "player_subtitlesmenu_resynctool_label": { + "message": "再同期ツール" + }, + "player_subtitlesmenu_savetool_label": { + "message": "字幕ファイルを保存" + }, + "player_subtitlesmenu_removetool_label": { + "message": "字幕トラックを削除" + }, + "player_subtitlesmenu_shifttool_label": { + "message": "$1 の字幕をシフト" + }, + "player_testsubtitle": { + "message": "これはテストの字幕です" + }, + "player_qualitymenu_label": { + "message": "動画の品質" + }, + "player_playbackrate_label": { + "message": "再生速度" + }, + "player_settings_title": { + "message": "FastStream の設定" + }, + "player_settings_closebtn_label": { + "message": "設定ウィンドウを閉じる" + }, + "player_settings_label": { + "message": "設定" + }, + "player_savevideo_label": { + "message": "ALT を押しながらで動画の一部を保存します。SHIFT を押しながらでバッファーをダンプします。" + }, + "player_screenshot_label": { + "message": "スクリーンショットを撮る" + }, + "player_pip_label": { + "message": "ピクチャー イン ピクチャー (PiP)" + }, + "player_fullscreen_label": { + "message": "フルスクリーン" + }, + "player_fragment_failed_singular": { + "message": "$1 の分割処理に失敗しました! クリックで再試行します。" + }, + "player_fragment_failed_plural": { + "message": "$1 の分割処理に失敗しました! クリックで再試行します。" + }, + "player_fragment_allbuffered": { + "message": "100% バッファー済み" + }, + "player_welcometext": { + "message": "FastStream v$1 へようこそ!" + }, + "player_nosource_alert": { + "message": "ソースは読み込まれていません!" + }, + "player_archive_loading": { + "message": "$1% のアーカイブを読み込み中..." + }, + "player_archive_loaded": { + "message": "アーカイブを読み込みました!" + }, + "player_archive_fail": { + "message": "アーカイブの読み込みに失敗しました!" + }, + "player_filename_prompt": { + "message": "ファイル名を入力" + }, + "player_screenshot_saving": { + "message": "スクリーンショットを撮る..." + }, + "player_screenshot_saved": { + "message": "スクリーンショットを保存しました!" + }, + "player_screenshot_fail": { + "message": "スクリーンショットの撮影に失敗しました!" + }, + "player_savevideo_inprogress_alert": { + "message": "すでに保存済みです!" + }, + "player_savevideo_unsupported": { + "message": "この動画は保存に対応していません!" + }, + "player_savevideo_partial_confirm": { + "message": "動画のダウンロードが完了していません! 本当に保存をしますか?" + }, + "player_savevideo_incognito_confirm": { + "message": "シークレットモードでは、RAM を使用して動画をバッファーします。コンピューターに動画全体を保存するのに必要なメモリがない可能性があります。\n続行してもよろしいですか?" + }, + "player_savevideo_start": { + "message": "保存中..." + }, + "player_savevideo_progress": { + "message": "$1% を保存中" + }, + "player_savevideo_fail": { + "message": "動画の保存に失敗しました!" + }, + "player_savevideo_failed_ask_archive": { + "message": "動画の保存に失敗しました!\n代わりにプレーヤーのバッファーストレージをアーカイブしますか?\n- アーカイブファイルをプレーヤーにドラッグ&ドロップで読み込みます" + }, + "player_savevideo_complete": { + "message": "保存が完了しました!" + }, + "player_archiver_progress": { + "message": "$1 をアーカイブ中" + }, + "player_archiver_saved": { + "message": "アーカイブを保存しました!" + }, + "player_quality_current": { + "message": "(現在)" + }, + "player_buffer_incognito_warning": { + "message": "シークレットモードでプリダウンロードに必要な空き容量がないため、$1 がバッファーされます" + }, + "player_buffer_storage_warning": { + "message": "プリダウンロードに必要な空き容量がないため、$1 がバッファーされます" + }, + "player_error_drm": { + "message": "読み込みに失敗しました! DRM には対応していません!" + }, + "player_error_load": { + "message": "動画の読み込みに失敗しました!" + }, + "player_source_autodetect": { + "message": "自動検出" + }, + "player_source_direct": { + "message": "ダイレクト" + }, + "player_source_accelmp4": { + "message": "高速化された MP4" + }, + "player_source_accelhls": { + "message": "高速化された HLS" + }, + "player_source_acceldash": { + "message": "高速化された DASH" + }, + "player_source_accelyt": { + "message": "高速化された YouTube" + }, + "player_source_mode": { + "message": "モード" + }, + "player_source_url_placeholder": { + "message": "ソースの URL" + }, + "player_source_headerbtn": { + "message": "ヘッダーを上書き ($1)" + }, + "player_source_headerbtn_label": { + "message": "ヘッダー上書き入力へ切り替え" + }, + "player_source_headerbtn_disabled": { + "message": "ヘッダーを上書き (拡張機能のみ)" + }, + "player_source_playbtn": { + "message": "再生" + }, + "player_source_playbtn_playing": { + "message": "再生中" + }, + "player_source_playbtn_loading": { + "message": "読み込み中..." + }, + "player_source_deletebtn": { + "message": "削除" + }, + "player_source_headers_label": { + "message": "ヘッダーの上書き入力" + }, + "player_source_headers_placeholder": { + "message": "ヘッダー名: ヘッダーの値\nヘッダー2の名: ヘッダー2の値" + }, + "player_source_nonelisted": { + "message": "ソースの一覧がありません" + }, + "player_source_onelisted": { + "message": "1 件のソースの一覧" + }, + "player_source_multilisted": { + "message": "$1 件のソースの一覧" + }, + "player_source_addbtn": { + "message": "ソースを追加" + }, + "player_source_clearbtn": { + "message": "ソースをクリア" + }, + "player_audioconfig_duplicate_profile": { + "message": "($1 のファイルから読み込み済み)" + }, + "player_audioconfig_create_profile": { + "message": "新規プロファイルを作成" + }, + "player_audioconfig_import_profile": { + "message": "ファイルからプロファイルをインポート" + }, + "player_audioconfig_import_invalid": { + "message": "無効なプロファイルファイル" + }, + "player_audioconfig_profile": { + "message": "プロファイル" + }, + "player_audioconfig_profile_unnamed": { + "message": "無題のプロファイル" + }, + "player_audioconfig_profile_load": { + "message": "プロファイルを読み込む" + }, + "player_audioconfig_profile_loaded": { + "message": "プロファイルを読み込みました!" + }, + "player_audioconfig_profile_save": { + "message": "プロファイルを保存" + }, + "player_audioconfig_profile_saving": { + "message": "保存中..." + }, + "player_audioconfig_profile_saved": { + "message": "プロファイルを保存しました!" + }, + "player_audioconfig_profile_download": { + "message": "プロファイルをダウンロード" + }, + "player_audioconfig_profile_downloaded": { + "message": "ダウンロードしました!" + }, + "player_audioconfig_profile_delete": { + "message": "削除" + }, + "player_audioconfig_profile_deleting": { + "message": "削除中..." + }, + "player_audioconfig_profile_deleted": { + "message": "削除しました!" + }, + "audiomixer_title": { + "message": "オーディオチャンネルミキサー" + }, + "audiomixer_solo_label": { + "message": "ソロ" + }, + "audiomixer_mute_label": { + "message": "消音" + }, + "audiocompressor_title": { + "message": "オーディオコンプレッサー" + }, + "audiocompressor_enabled": { + "message": "コンプレッサー有効" + }, + "audiocompressor_disabled": { + "message": "コンプレッサー無効" + }, + "audiocompressor_threshold": { + "message": "スレッショルド" + }, + "audiocompressor_knee": { + "message": "ニー" + }, + "audiocompressor_ratio": { + "message": "レシオ" + }, + "audiocompressor_attack": { + "message": "アタック" + }, + "audiocompressor_release": { + "message": "リリース" + }, + "audiocompressor_gain": { + "message": "ゲイン" + }, + "audioeq_title": { + "message": "オーディオイコライザー" + }, + "audioeq_instructions": { + "message": "ダブルクリックでタイプを変更" + }, + "audioeq_gain": { + "message": "ゲイン: $1dB" + }, + "audioeq_qscroll": { + "message": "スクロールで Q を変更" + }, + "options_title": { + "message": "FastStream のオプション" + }, + "options_promo_header": { + "message": "FastStream にご満足いただけましたか? 評価をお願いします!" + }, + "options_promo_body": { + "message": "FastStream はボランティアによって維持されている趣味なプロジェクトです。改善すべき事を知るためにフィードバックをいただければ幸いです。お気軽にお申し付けください:" + }, + "options_promo_l1": { + "message": "使用方法" + }, + "options_promo_l2": { + "message": "バグに遭遇した場合" + }, + "options_promo_l3": { + "message": "機能のリクエスト" + }, + "options_promo_end": { + "message": "私たちはアクセシビリティについても非常に重視をしています。現在のバージョンに不足している機能があった場合は、リクエストをしてください。できるだけ早く対応をします。" + }, + "options_promo_rate": { + "message": "OK、FastStream を評価する" + }, + "options_promo_norate": { + "message": "評価しない :(" + }, + "options_video_header": { + "message": "動画のオプション" + }, + "options_video_body": { + "message": "動画に CSS フィルタを適用します。ピクチャー イン ピクチャー (PiP) では動作しません。" + }, + "options_video_brightness": { + "message": "明るさ" + }, + "options_video_contrast": { + "message": "コントラスト" + }, + "options_video_saturation": { + "message": "彩度" + }, + "options_video_grayscale": { + "message": "グレースケール" + }, + "options_video_sepia": { + "message": "セピア" + }, + "options_video_invert": { + "message": "反転" + }, + "options_video_hue": { + "message": "色相の回転" + }, + "options_general_header": { + "message": "一般設定" + }, + "options_general_predownload": { + "message": "可能な場合は動画全体をバックグラウンドで事前にダウンロードする" + }, + "options_general_targetspeed": { + "message": "プリダウンローダーのターゲット速度" + }, + "options_general_freeunused": { + "message": "品質レベル切り替え時に未使用のチャンネルバッファーを解放する" + }, + "options_general_analyze": { + "message": "イントロ/アウトロが付いた動画を自動的に解析する\n(CPU パフォーマンスに問題がある場合は OFF にしてください)" + }, + "options_general_autosub": { + "message": "自動的に最適な字幕トラックを有効化する\n(字幕設定でデフォルトの言語を変更できます)" + }, + "options_general_stream": { + "message": "プレイリスト URL (.m3u8/.mpd) を開くときに HLS/DASH ストリームを再生するプレーヤーを使用する" + }, + "options_general_mp4": { + "message": "動画の URL (.mp4) を開くときにMP4 の動画を再生するプレーヤーを使用する" + }, + "options_general_seekstep": { + "message": "シークキーバインドのベースステップサイズ (秒)" + }, + "options_keybinds_header": { + "message": "キーボードショートカット" + }, + "options_keybinds_body": { + "message": "キーバインドはプレーヤーをフォーカス時のみ有効です。" + }, + "options_keybinds_reset": { + "message": "デフォルトにリセット" + }, + "options_autourl_header": { + "message": "URL で自動的に有効化" + }, + "options_autourl_body": { + "message": "これらの URL にアクセスすると自動的に拡張機能が有効化され、離れる事で無効化されます。1 行に 1 つの URL があり、その URL で始まるページも一致します。正規表現の場合は、先頭にチルダ (~) を付けます。1 行に 1 つの正規表現となります。" + }, + "options_autourl_hint": { + "message": "ヒント: 正規表現のテストができます" + }, + "options_help_header": { + "message": "ヘルプ" + }, + "options_help_welcome": { + "message": "ようこそのページ" + }, + "options_help_issues": { + "message": "Issue Tracker" + } +} From 809fd5658ece073e5c105b553d603287123a323a Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 06:57:14 -0600 Subject: [PATCH 08/20] Add reindex-ot to contributors list --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index afebbe6a..e48c9423 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ You can then install the extension temporarily on Firefox Developer Edition by g Many thanks to the contributors of this project. - Dael (dael_io): Fixed Spanish translations +- reindex-ot: Japanese translations ## Technical Details @@ -153,4 +154,4 @@ url_8/193039199_mp4_h264_aac_fhd_7.m3u8 (I'm not a lawyer, don't take this as legal advice but do pay attention) -While it may be possible for FastStream to save videos from any website as long as there is no DRM, that doesn't mean you have the legal right to do so if you don't own the content. Please be mindful of how you use this tool. FastStream should not be used to infringe copyright. \ No newline at end of file +While it may be possible for FastStream to save videos from any website as long as there is no DRM, that doesn't mean you have the legal right to do so if you don't own the content. Please be mindful of how you use this tool. FastStream should not be used to infringe copyright. From d4ddd59b0999258a8ea2451c2c026bc6201aa76b Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 08:51:58 -0600 Subject: [PATCH 09/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e48c9423..4d46106b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The player currently supports: - MP4 videos - HLS streams - DASH streams -- Youtube (experimental, download only supported on Chrome when manually installed) +- Youtube (download not supported on Chrome unless manually installed) Player features: - OpenSubtitles support so you can search for subtitles directly from the player. From a0f6f4699d146d04b2c29abe84a5a5ee1e5e509b Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 09:05:21 -0600 Subject: [PATCH 10/20] Fix intro/outro skip --- chrome/player/ui/InterfaceController.mjs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/chrome/player/ui/InterfaceController.mjs b/chrome/player/ui/InterfaceController.mjs index 6e7ef7f4..bcdc20d0 100644 --- a/chrome/player/ui/InterfaceController.mjs +++ b/chrome/player/ui/InterfaceController.mjs @@ -1323,6 +1323,9 @@ export class InterfaceController { }); } + let currentSegment = null; + const time = this.client.currentTime; + skipSegments.forEach((segment) => { const segmentElement = document.createElement('div'); segmentElement.classList.add('skip_segment'); @@ -1330,18 +1333,19 @@ export class InterfaceController { segmentElement.style.left = segment.startTime / duration * 100 + '%'; segmentElement.style.width = (segment.endTime - segment.startTime) / duration * 100 + '%'; DOMElements.skipSegmentsContainer.appendChild(segmentElement); + + if (!currentSegment && time >= segment.startTime && time < segment.endTime) { + currentSegment = segment; + segmentElement.classList.add('active'); + } }); this.skipSegments = skipSegments; - const time = this.client.currentTime; - const currentSegment = skipSegments.find((segment) => time >= segment.startTime && time < segment.endTime); - if (currentSegment) { DOMElements.skipButton.style.display = ''; DOMElements.skipButton.textContent = currentSegment.skipText; DOMElements.progressContainer.classList.add('skip_freeze'); - currentSegment.classList.add('active'); } else { DOMElements.progressContainer.classList.remove('skip_freeze'); DOMElements.skipButton.style.display = 'none'; From d9faf19ab269f2198c87d7ac5b9b749d8efe1a42 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 09:10:44 -0600 Subject: [PATCH 11/20] Add debug demo method --- chrome/player/FastStreamClient.mjs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/chrome/player/FastStreamClient.mjs b/chrome/player/FastStreamClient.mjs index d05a5bb1..b2ec4d25 100644 --- a/chrome/player/FastStreamClient.mjs +++ b/chrome/player/FastStreamClient.mjs @@ -964,5 +964,17 @@ export class FastStreamClient extends EventEmitter { get currentVideo() { return this.player?.getVideo() || null; } + + debugDemo() { + this.interfaceController.hideControlBar = ()=>{}; + + this.videoAnalyzer.introAligner.detectedStartTime = 0; + this.videoAnalyzer.introAligner.detectedEndTime = 30; + this.videoAnalyzer.introAligner.found = true; + this.videoAnalyzer.introAligner.emit('match', true); + + this.currentTime = 6; + this.player.getVideo().style.objectFit = 'cover'; + } } From 6ea639b98c863974f7bdd56e77a926c34dcc25b5 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 09:26:27 -0600 Subject: [PATCH 12/20] Reset error on source change --- chrome/player/ui/InterfaceController.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/chrome/player/ui/InterfaceController.mjs b/chrome/player/ui/InterfaceController.mjs index bcdc20d0..1948ffeb 100644 --- a/chrome/player/ui/InterfaceController.mjs +++ b/chrome/player/ui/InterfaceController.mjs @@ -137,6 +137,7 @@ export class InterfaceController { this.progressCacheAudio = []; this.hasShownSkip = false; this.failed = false; + this.setStatusMessage('error', null, 'error'); this.reuseDownloadURL = false; if (this.downloadURL) { URL.revokeObjectURL(this.downloadURL); From 9d7304eae3903ea4ffe75a7c0b9e158eee66a526 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 09:42:00 -0600 Subject: [PATCH 13/20] Fixes to subsyncer --- chrome/player/assets/fluidplayer/css/fluidplayer.css | 2 +- chrome/player/ui/subtitles/SubtitleSyncer.mjs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/chrome/player/assets/fluidplayer/css/fluidplayer.css b/chrome/player/assets/fluidplayer/css/fluidplayer.css index 459ebe41..60872f16 100644 --- a/chrome/player/assets/fluidplayer/css/fluidplayer.css +++ b/chrome/player/assets/fluidplayer/css/fluidplayer.css @@ -1432,7 +1432,7 @@ body { box-sizing: border-box; height: 20px; background-color: var(--timeline-cue-background-color); - border-radius: 2px; + border-radius: 3px; font-size: 12px; color: var(--timeline-cue-text-color); padding: 0px 2px; diff --git a/chrome/player/ui/subtitles/SubtitleSyncer.mjs b/chrome/player/ui/subtitles/SubtitleSyncer.mjs index 25a476d9..941b39b7 100644 --- a/chrome/player/ui/subtitles/SubtitleSyncer.mjs +++ b/chrome/player/ui/subtitles/SubtitleSyncer.mjs @@ -324,12 +324,16 @@ export class SubtitleSyncer extends EventEmitter { }); this.visibleCues.forEach((cue) => { + if (cue.text.length === 0) return; if (this.trackElements.find((el) => el.cue === cue)) return; const el = WebUtils.create('div', '', 'timeline_track_cue'); el.style.left = cue.startTime / this.video.duration * 100 + '%'; el.style.width = (cue.endTime - cue.startTime) / this.video.duration * 100 + '%'; - el.appendChild(WebVTT.convertCueToDOMTree(window, cue.text)); + if (!cue.dom) { + cue.dom = WebVTT.convertCueToDOMTree(window, cue.text); + } + el.appendChild(cue.dom); el.title = cue.text; this.ui.timelineTrack.appendChild(el); From d72894d56c61d2cdcdf525448439063aa4926c39 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 09:45:45 -0600 Subject: [PATCH 14/20] Fixes to subsyncer --- chrome/_locales/en/messages.json | 2 +- chrome/_locales/es/messages.json | 2 +- chrome/_locales/ja/messages.json | 2 +- chrome/player/ui/subtitles/SubtitleSyncer.mjs | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/chrome/_locales/en/messages.json b/chrome/_locales/en/messages.json index 37a99729..00a12338 100644 --- a/chrome/_locales/en/messages.json +++ b/chrome/_locales/en/messages.json @@ -421,7 +421,7 @@ "message": "Save complete!" }, "player_archiver_progress": { - "message": "Archiving $1" + "message": "Archiving $1%" }, "player_archiver_saved": { "message": "Archive saved!" diff --git a/chrome/_locales/es/messages.json b/chrome/_locales/es/messages.json index 3cb4f689..84bc5d43 100644 --- a/chrome/_locales/es/messages.json +++ b/chrome/_locales/es/messages.json @@ -420,7 +420,7 @@ "message": "¡Guardado completo!" }, "player_archiver_progress": { - "message": "Archivando $1" + "message": "Archivando $1%" }, "player_archiver_saved": { "message": "¡Archivo guardado!" diff --git a/chrome/_locales/ja/messages.json b/chrome/_locales/ja/messages.json index 869d23ef..780d63f1 100644 --- a/chrome/_locales/ja/messages.json +++ b/chrome/_locales/ja/messages.json @@ -421,7 +421,7 @@ "message": "保存が完了しました!" }, "player_archiver_progress": { - "message": "$1 をアーカイブ中" + "message": "$1% をアーカイブ中" }, "player_archiver_saved": { "message": "アーカイブを保存しました!" diff --git a/chrome/player/ui/subtitles/SubtitleSyncer.mjs b/chrome/player/ui/subtitles/SubtitleSyncer.mjs index 941b39b7..ed25e841 100644 --- a/chrome/player/ui/subtitles/SubtitleSyncer.mjs +++ b/chrome/player/ui/subtitles/SubtitleSyncer.mjs @@ -330,10 +330,10 @@ export class SubtitleSyncer extends EventEmitter { const el = WebUtils.create('div', '', 'timeline_track_cue'); el.style.left = cue.startTime / this.video.duration * 100 + '%'; el.style.width = (cue.endTime - cue.startTime) / this.video.duration * 100 + '%'; - if (!cue.dom) { - cue.dom = WebVTT.convertCueToDOMTree(window, cue.text); + if (!cue.dom2) { + cue.dom2 = WebVTT.convertCueToDOMTree(window, cue.text); } - el.appendChild(cue.dom); + el.appendChild(cue.dom2); el.title = cue.text; this.ui.timelineTrack.appendChild(el); From 9969d5cbc04f46d50501cc4a0540798037e079d9 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 10:05:15 -0600 Subject: [PATCH 15/20] UI Improvements --- .../player/assets/fluidplayer/css/fluidplayer.css | 1 + chrome/player/ui/InterfaceController.mjs | 13 +++++++++---- chrome/player/ui/audio/AudioConfigManager.mjs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/chrome/player/assets/fluidplayer/css/fluidplayer.css b/chrome/player/assets/fluidplayer/css/fluidplayer.css index 60872f16..401d6e90 100644 --- a/chrome/player/assets/fluidplayer/css/fluidplayer.css +++ b/chrome/player/assets/fluidplayer/css/fluidplayer.css @@ -1109,6 +1109,7 @@ body { z-index: 2; transition: bottom 0.5s; cursor: auto; + outline: none; } .popwindow .title { diff --git a/chrome/player/ui/InterfaceController.mjs b/chrome/player/ui/InterfaceController.mjs index 1948ffeb..f2593179 100644 --- a/chrome/player/ui/InterfaceController.mjs +++ b/chrome/player/ui/InterfaceController.mjs @@ -985,11 +985,16 @@ export class InterfaceController { async dumpBuffer(name) { const entries = this.client.downloadManager.getCompletedEntries(); const filestream = streamSaver.createWriteStream(name + '.fsa'); - await FastStreamArchiveUtils.writeFSAToStream(filestream, this.client.player, entries, (progress)=>{ - this.setStatusMessage('save-video', Localize.getMessage('player_archiver_progress', [Math.floor(progress * 100)]), 'info'); - }); + try { + await FastStreamArchiveUtils.writeFSAToStream(filestream, this.client.player, entries, (progress)=>{ + this.setStatusMessage('save-video', Localize.getMessage('player_archiver_progress', [Math.floor(progress * 100)]), 'info'); + }); - this.setStatusMessage('save-video', Localize.getMessage('player_archiver_saved'), 'info', 2000); + this.setStatusMessage('save-video', Localize.getMessage('player_archiver_saved'), 'info', 2000); + } catch (e) { + console.error(e); + this.setStatusMessage('save-video', 'Unreachable Error', 'error', 2000); + } } updateMarkers() { diff --git a/chrome/player/ui/audio/AudioConfigManager.mjs b/chrome/player/ui/audio/AudioConfigManager.mjs index 9daf16f4..de77068c 100644 --- a/chrome/player/ui/audio/AudioConfigManager.mjs +++ b/chrome/player/ui/audio/AudioConfigManager.mjs @@ -237,8 +237,8 @@ export class AudioConfigManager extends EventEmitter { if (e.key === 'Escape') { this.closeUI(); e.preventDefault(); + e.stopPropagation(); } - e.stopPropagation(); }); DOMElements.audioConfigContainer.addEventListener('keyup', (e) => { From 851f24e2b2285dc73582c6d8e1f2365b2dd26b7a Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 10:07:41 -0600 Subject: [PATCH 16/20] Allow keybinds to be used within popwindow --- chrome/player/ui/OptionsWindow.mjs | 2 +- chrome/player/ui/SourcesBrowser.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chrome/player/ui/OptionsWindow.mjs b/chrome/player/ui/OptionsWindow.mjs index 4ced53a5..86d4e597 100644 --- a/chrome/player/ui/OptionsWindow.mjs +++ b/chrome/player/ui/OptionsWindow.mjs @@ -16,8 +16,8 @@ export class OptionsWindow { if (e.key === 'Escape') { this.closeUI(); e.preventDefault(); + e.stopPropagation(); } - e.stopPropagation(); }); DOMElements.optionsContainer.addEventListener('keyup', (e) => { diff --git a/chrome/player/ui/SourcesBrowser.mjs b/chrome/player/ui/SourcesBrowser.mjs index 0a8ea837..c1d11eda 100644 --- a/chrome/player/ui/SourcesBrowser.mjs +++ b/chrome/player/ui/SourcesBrowser.mjs @@ -214,8 +214,8 @@ export class SourcesBrowser { if (e.key === 'Escape') { this.closeUI(); e.preventDefault(); + e.stopPropagation(); } - e.stopPropagation(); }); DOMElements.linkuiContainer.addEventListener('keyup', (e) => { From 90d556d7e0550f6d31200dd5596f1532c08a23c7 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 10:15:03 -0600 Subject: [PATCH 17/20] Don't propagate key events in inputs --- chrome/player/ui/SourcesBrowser.mjs | 8 ++++++++ .../ui/subtitles/OpenSubtitlesSearch.mjs | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/chrome/player/ui/SourcesBrowser.mjs b/chrome/player/ui/SourcesBrowser.mjs index c1d11eda..38def87e 100644 --- a/chrome/player/ui/SourcesBrowser.mjs +++ b/chrome/player/ui/SourcesBrowser.mjs @@ -79,6 +79,10 @@ export class SourcesBrowser { } }); + sourceURL.addEventListener('keydown', (e) => { + e.stopPropagation(); + }); + const sourceHeadersBtn = WebUtils.create('div', null, 'linkui-source-headers-button'); sourceHeadersBtn.textContent = Localize.getMessage('player_source_headerbtn', [Object.keys(source.headers).length]); sourceHeadersBtn.title = Localize.getMessage('player_source_headerbtn_label'); @@ -145,6 +149,10 @@ export class SourcesBrowser { this.updateSources(); }); + headersInput.addEventListener('keydown', (e) => { + e.stopPropagation(); + }); + headersInput.style.display = 'none'; sourceContainer.appendChild(headersInput); diff --git a/chrome/player/ui/subtitles/OpenSubtitlesSearch.mjs b/chrome/player/ui/subtitles/OpenSubtitlesSearch.mjs index 3ddf94c7..885cf2eb 100644 --- a/chrome/player/ui/subtitles/OpenSubtitlesSearch.mjs +++ b/chrome/player/ui/subtitles/OpenSubtitlesSearch.mjs @@ -50,8 +50,8 @@ export class OpenSubtitlesSearch extends EventEmitter { if (e.key === 'Escape') { this.closeUI(); e.preventDefault(); + e.stopPropagation(); } - e.stopPropagation(); }); DOMElements.subuiContainer.addEventListener('keyup', (e) => { @@ -77,6 +77,10 @@ export class OpenSubtitlesSearch extends EventEmitter { const searchInput = WebUtils.create('input', null, 'text_input'); searchInput.placeholder = Localize.getMessage('player_opensubtitles_search_placeholder'); searchInput.classList.add('subtitle-search-input'); + searchInput.addEventListener('keydown', (e) => { + e.stopPropagation(); + }); + this.subui.searchContainer.appendChild(searchInput); this.subui.search = searchInput; @@ -91,11 +95,17 @@ export class OpenSubtitlesSearch extends EventEmitter { seasonInput.placeholder = Localize.getMessage('player_opensubtitles_seasonnum'); seasonInput.classList.add('subtitle-season-input'); seasonInput.style.display = 'none'; + seasonInput.addEventListener('keydown', (e) => { + e.stopPropagation(); + }); const episodeInput = WebUtils.create('input', null, 'text_input'); episodeInput.placeholder = Localize.getMessage('player_opensubtitles_episodenum'); episodeInput.classList.add('subtitle-episode-input'); episodeInput.style.display = 'none'; + episodeInput.addEventListener('keydown', (e) => { + e.stopPropagation(); + }); const typeSelector = WebUtils.createDropdown('all', 'Type', { @@ -125,12 +135,17 @@ export class OpenSubtitlesSearch extends EventEmitter { languageInput.classList.add('subtitle-language-input'); this.subui.searchContainer.appendChild(languageInput); this.subui.languageInput = languageInput; + languageInput.addEventListener('keydown', (e) => { + e.stopPropagation(); + }); const yearInput = WebUtils.create('input', null, 'text_input'); yearInput.placeholder = Localize.getMessage('player_opensubtitles_year'); yearInput.classList.add('subtitle-year-input'); this.subui.searchContainer.appendChild(yearInput); - + yearInput.addEventListener('keydown', (e) => { + e.stopPropagation(); + }); const sortSelector = WebUtils.createDropdown('download_count', Localize.getMessage('player_opensubtitles_sortby'), { From 9a6df1ccf74ff79d6abc1b69f2e8dca7f60750b4 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 10:21:59 -0600 Subject: [PATCH 18/20] Cleanup WebUtils --- chrome/player/utils/WebUtils.mjs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/chrome/player/utils/WebUtils.mjs b/chrome/player/utils/WebUtils.mjs index 5cb1b916..18c19efa 100644 --- a/chrome/player/utils/WebUtils.mjs +++ b/chrome/player/utils/WebUtils.mjs @@ -214,17 +214,13 @@ export class WebUtils { if (e.key === 'Enter') { span.blur(); e.stopPropagation(); - e.preventDefault(); } + e.preventDefault(); }); span.addEventListener('click', (e) => { e.stopPropagation(); }); - - span.addEventListener('keydown', (e) => { - e.stopPropagation(); - }); } return container; } From 032f6a722dba91dbc50951ada1729e2a0cd9087d Mon Sep 17 00:00:00 2001 From: Andrew S Date: Wed, 22 Nov 2023 11:12:08 -0600 Subject: [PATCH 19/20] V1.2.4 --- chrome/manifest.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chrome/manifest.json b/chrome/manifest.json index 155814c4..0def47f8 100644 --- a/chrome/manifest.json +++ b/chrome/manifest.json @@ -3,7 +3,7 @@ "default_locale": "en", "name": "__MSG_extension_name__", "description": "__MSG_extension_description__", - "version": "1.2.3", + "version": "1.2.4", "author": "Andrew S", "options_ui": { "page": "options/options.html", diff --git a/package.json b/package.json index 465d37e0..112eb859 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "faststream", - "version": "1.2.3", + "version": "1.2.4", "description": "Stream without buffering", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", From a28d82fe2595977eb939c4c2e8f91824fbd09327 Mon Sep 17 00:00:00 2001 From: Andrew S Date: Thu, 23 Nov 2023 00:40:57 -0600 Subject: [PATCH 20/20] Cleanup README.md Add banner image and move technical details to wiki. --- README.md | 79 +++---------------------------------------------------- 1 file changed, 4 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 4d46106b..c87e0ab9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# FastStream 2 +![logotext1](https://github.com/Andrews54757/FastStream/assets/13282284/cf344807-ff49-4db2-b806-4be5458fd767) + +# FastStream Tired of having to wait while videos buffer? This extension will replace videos on websites with a custom player that is designed to play with minimal buffering. @@ -75,80 +77,7 @@ Many thanks to the contributors of this project. ## Technical Details -### FastStream Archive Files (.fsa) - -In some select cases, it is not feasible to convert the contents of a buffer into an MP4 on the browser (not without ffmpeg). Moreover, the buffer's required metadata varies depending on the media type. For this reason, we propose a new file format to store arbitrary information with ultrafast writing/parsing speeds for the browser environment. - -#### Structure -Using example stream `https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8`. Numerical values use Big Endian representation. - -- `00 00 01 00`: 4 byte header size (256) -- `FSA Header`: JSON header encoded as utf8 (see below) -```json -{ - "version": 1, - "number_of_entries": 66, - "source": { - "url": "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8", - "identifier": "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8", - "mode": "accelerated_hls", - "headers": {} - }, - "currentLevel": "0:4", - "currentAudioLevel": "1:-1" -} -``` - -This header is followed by the entries. Each entry has the following format: - -- `00 00 01 C8`: 4 byte entry header size (456) -- JSON entry header encoded as utf8 (See below). Only whitelisted response headers are included (eg: content-range). -```json -{ - "url": "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8", - "responseType": "text", - "stats": { - "aborted": false, - "timedout": false, - "loaded": 752, - "total": 752, - "retry": 0, - "chunkCount": 0, - "bwEstimate": 0, - "loading": { - "start": 112.39999997615814, - "first": 115.79999995231628, - "end": 116 - }, - "parsing": { - "start": 1083.2000000476837, - "end": 0 - }, - "buffering": { - "start": 0, - "first": 0, - "end": 0 - } - }, - "responseURL": "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8", - "responseHeaders": {}, - "dataSize": 752 -} -``` -- `Entry data`: `dataSize` bytes containing the data (example below) -```m3u8 -#EXTM3U -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2149280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=1280x720,NAME="720" -url_0/193039199_mp4_h264_aac_hd_7.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=246440,CODECS="mp4a.40.5,avc1.42000d",RESOLUTION=320x184,NAME="240" -url_2/193039199_mp4_h264_aac_ld_7.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=460560,CODECS="mp4a.40.5,avc1.420016",RESOLUTION=512x288,NAME="380" -url_4/193039199_mp4_h264_aac_7.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x480,NAME="480" -url_6/193039199_mp4_h264_aac_hq_7.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=6221600,CODECS="mp4a.40.2,avc1.640028",RESOLUTION=1920x1080,NAME="1080" -url_8/193039199_mp4_h264_aac_fhd_7.m3u8 -``` +Please see the [wiki](https://github.com/Andrews54757/FastStream/wiki/Technical-Details) for more information on the technical details! ## Disclaimer