diff --git a/app/manifest.json b/app/manifest.json index 5eef1ea..80aa4ff 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -16,6 +16,7 @@ { "matches": [ "https://www.nicovideo.jp/watch/*", + "https://www.nicovideo.jp/watch_tmp/*", "https://live.nicovideo.jp/watch/*", "https://live2.nicovideo.jp/watch/*" ], diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..53ae3b7 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,78 @@ +export const EXTENSION_LABEL = '[非公式] PinP'; + +/* + * video タグのセレクタ + */ +// ニコニコ動画 +export const NICO_VIDEO_VIDEO_TAG_SELECTOR = '#MainVideoPlayer video'; +// ニコニコ動画(Re:仮) +export const NICO_VIDEO_TMP_VIDEO_TAG_SELECTOR = 'main [data-name="content"] video'; +// ニコニコ生放送 +export const NICO_LIVE_VIDEO_TAG_SELECTOR = '[class^=___video-layer___] video'; + +export const VIDEO_TAG_SELECTOR = [ + NICO_VIDEO_VIDEO_TAG_SELECTOR, + NICO_VIDEO_TMP_VIDEO_TAG_SELECTOR, + NICO_LIVE_VIDEO_TAG_SELECTOR, +].join(','); + +/* + * コメント用 canvas のセレクタ + */ +// ニコニコ動画 +export const NICO_VIDEO_COMMENT_CANVAS_TAG_SELECTOR = '.CommentRenderer canvas'; +// ニコニコ動画(Re:仮) +export const NICO_VIDEO_TMP_COMMENT_CANVAS_TAG_SELECTOR = 'main [data-name="comment"] > canvas'; +// ニコニコ生放送 +export const NICO_LIVE_COMMENT_CANVAS_TAG_SELECTOR = '[class^=___comment-layer___] canvas'; + +export const COMMENT_CANVAS_TAG_SELECTOR = [ + NICO_VIDEO_COMMENT_CANVAS_TAG_SELECTOR, + NICO_VIDEO_TMP_COMMENT_CANVAS_TAG_SELECTOR, + NICO_LIVE_COMMENT_CANVAS_TAG_SELECTOR, +].join(','); + +/* + * コピー元とするボタンのセレクタ + */ +// ニコニコ動画 +export const NICO_VIDEO_TARGET_BUTTON_SELECTOR = '.ControllerContainer .EnableFullScreenButton'; +// ニコニコ動画(Re:仮) +export const NICO_VIDEO_TMP_TARGET_BUTTON_SELECTOR = 'main .w_24px:not(.p_2px)'; +// ニコニコ生放送 +export const NICO_LIVE_TARGET_BUTTON_SELECTOR = '[class^=___addon-controller___] [class^=___fullscreen-button___]'; + +export const TARGET_BUTTON_SELECTOR = [ + NICO_VIDEO_TARGET_BUTTON_SELECTOR, + NICO_VIDEO_TMP_TARGET_BUTTON_SELECTOR, + NICO_LIVE_TARGET_BUTTON_SELECTOR, +].join(','); + +/** + * 提供画面の HTML のセレクタ(ニコニコ動画のみ) + */ +export const NICO_VIDEO_SUPPORTER_VIEW_SELECTOR = '#UadPlayer .SupporterView'; +export const NICO_VIDEO_TMP_SUPPORTER_VIEW_SELECTOR = 'main [data-name="supporter-content"]'; + +export const SUPPORTER_VIEW_SELECTOR = [ + NICO_VIDEO_SUPPORTER_VIEW_SELECTOR, + NICO_VIDEO_TMP_SUPPORTER_VIEW_SELECTOR, +].join(','); + +/** + * 提供画面の Canvas のセレクタ(ニコニコ動画のみ) + */ +export const NICO_VIDEO_SUPPORTER_VIEW_CANVAS_SELECTOR = '#SupporterView-canvas'; +export const NICO_VIDEO_TMP_SUPPORTER_VIEW_CANVAS_SELECTOR = 'main [data-name="supporter-content"] canvas'; + +export const SUPPORTER_VIEW_CANVAS_SELECTOR = [ + NICO_VIDEO_SUPPORTER_VIEW_CANVAS_SELECTOR, + NICO_VIDEO_TMP_SUPPORTER_VIEW_CANVAS_SELECTOR, +].join(','); + +/** + * Akashic のゲーム画面の canvas のセレクタ(ニコニコ生放送のみ) + */ +export const NICO_LIVE_AKASHIC_CANVAS_SELECTOR = '#akashic-gameview canvas'; + +export const AKASHIC_CANVAS_SELECTOR = [NICO_LIVE_AKASHIC_CANVAS_SELECTOR].join(','); diff --git a/src/contentScript.ts b/src/contentScript.ts index 4cf0986..3d00c40 100644 --- a/src/contentScript.ts +++ b/src/contentScript.ts @@ -1,41 +1,48 @@ +import { EXTENSION_LABEL, TARGET_BUTTON_SELECTOR, VIDEO_TAG_SELECTOR } from './constants'; import { copyButton } from './functions/copyButton'; import { initPinP, startPinP } from './functions/pinp'; import PictureInPictureIcon from './svg/pinpIcon'; -const TITLE = '[非公式] PinP'; - -const checkPinPButton = setInterval(function () { - const fullScreenButton = document.querySelector( - '.ControllerContainer .EnableFullScreenButton, [class^=___addon-controller___] [class^=___fullscreen-button___]' - ); - if (!fullScreenButton) return; +(() => { + // ページにスクリプトを挿入 + // ※主にギフト画像の読み込み問題用。解消したら削除したい。 + const path = chrome.runtime.getURL('js/inject.js'); + const script = document.createElement('script'); + script.src = path; + document.head.append(script); - initPinP(); + let count = 0; + const checkPinPButton = setInterval(function () { + const targetButtons = document.querySelectorAll(TARGET_BUTTON_SELECTOR); + if (targetButtons.length === 0) { + // 一定回数チェックして見つからなかったらページ構造が変わったと判断して終了 + ++count; + if (count > 15) clearInterval(checkPinPButton); + return; + } - // フルスクボタンをコピーしPinPボタンにする - copyButton({ - srcButton: fullScreenButton, - title: TITLE, - icon: PictureInPictureIcon, - onClick: startPinP, - }); + // PinPの初期化 + initPinP(); - // videoのPinP操作からstartPinPを呼び出すようにする - const video = document.querySelector('#MainVideoPlayer video, [class^=___video-layer___] video'); - if (video) { - video.disablePictureInPicture = false; - video.addEventListener('enterpictureinpicture', (e) => { - e.preventDefault(); - startPinP(); + // ボタンをコピーしPinPボタンにする + const targetButton = targetButtons[targetButtons.length - 1]; + copyButton({ + srcButton: targetButton, + title: EXTENSION_LABEL, + icon: PictureInPictureIcon, + onClick: startPinP, }); - } - clearInterval(checkPinPButton); -}, 500); + // videoのPinP操作からstartPinPを呼び出すようにする + const video = document.querySelector(VIDEO_TAG_SELECTOR); + if (video) { + video.disablePictureInPicture = false; + video.addEventListener('enterpictureinpicture', (e) => { + e.preventDefault(); + startPinP(); + }); + } -(() => { - const path = chrome.runtime.getURL('js/inject.js'); - const script = document.createElement('script'); - script.src = path; - document.head.append(script); + clearInterval(checkPinPButton); + }, 500); })(); diff --git a/src/functions/copyButton.ts b/src/functions/copyButton.ts index d05664f..1f3a6b6 100644 --- a/src/functions/copyButton.ts +++ b/src/functions/copyButton.ts @@ -8,6 +8,8 @@ interface CopyButtonParameter { export function copyButton({ srcButton, title, icon, onClick }: CopyButtonParameter): void { const cloneButton = srcButton.cloneNode(true) as HTMLElement; + cloneButton.setAttribute('title', title); + // ニコ動 if (cloneButton.hasAttribute('data-title')) { cloneButton.setAttribute('data-title', title); diff --git a/src/functions/pinp.ts b/src/functions/pinp.ts index b826757..775171f 100644 --- a/src/functions/pinp.ts +++ b/src/functions/pinp.ts @@ -1,3 +1,11 @@ +import { + AKASHIC_CANVAS_SELECTOR, + COMMENT_CANVAS_TAG_SELECTOR, + SUPPORTER_VIEW_CANVAS_SELECTOR, + SUPPORTER_VIEW_SELECTOR, + VIDEO_TAG_SELECTOR, +} from '../constants'; + const FRAME_RATE = 60; let isHidden = false; @@ -53,15 +61,11 @@ function calcSize(srcWidth: number, srcHeight: number, dstWidth: number, dstHeig export function handleVideo(): void { const myId = ++uid; - const comment = document.querySelector( - '.CommentRenderer canvas, [class^=___comment-layer___] canvas' - ); - const targetVideo = document.querySelector( - '#MainVideoPlayer video, [class^=___video-layer___] video' - ); - const supporterView = document.querySelector('#UadPlayer .SupporterView'); - const supporterCanvas = document.getElementById('SupporterView-canvas') as HTMLCanvasElement; - const akashicCanvas = document.querySelector('#akashic-gameview canvas'); + const comment = document.querySelector(COMMENT_CANVAS_TAG_SELECTOR); + const targetVideo = document.querySelector(VIDEO_TAG_SELECTOR); + const supporterView = document.querySelector(SUPPORTER_VIEW_SELECTOR); + const supporterCanvas = document.querySelector(SUPPORTER_VIEW_CANVAS_SELECTOR); + const akashicCanvas = document.querySelector(AKASHIC_CANVAS_SELECTOR); function update() { if (!comment || !targetVideo || !context || myId != uid) { @@ -97,7 +101,13 @@ export function handleVideo(): void { ); // supporter - if (supporterView?.style.visibility === 'visible' && supporterCanvas) { + if ( + // ニコニコ動画 + (supporterView?.style.visibility === 'visible' || + // ニコニコ動画(Re:仮) + (supporterView?.style.visibility === '' && supporterView?.style.display === 'block')) && + supporterCanvas + ) { const supporterSize = calcSize(supporterCanvas.width, supporterCanvas.height, canvas.width, canvas.height); context.drawImage( supporterCanvas,