/
script.js
450 lines (411 loc) · 17.1 KB
/
script.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
$(function() {
var settings = {
// オプションで変更可能なキーコード
togglePlayAndPauseKeyCode: 'k',
jumpToBeginningKeyCode: 'h',
jumpToEndKeyCode: 'e',
prevFrameKeyCode: 'j',
nextFrameKeyCode: 'l',
volumeDownKeyCode: 'd',
volumeUpKeyCode: 'u',
toggleMuteKeyCode: 'm',
toggleRepeatPlayKeyCode: 'r',
toggleViewCommentKeyCode: 'v',
jumpToSpecifiedFrameKeyCode: 't',
backToBeforeFrameKeyCode: 'b',
changeScreenModeKeyCode: 'f',
onbeforeunloadWarning: true,
checkCommandAvailability: false,
scrollToPlayerKeyCode: true,
};
var fixed = {
// 固定のキーコード
togglePlayAndPauseKeyCode: ' ', // space
prevFrameKeyCode: 'ArrowLeft', // left-arrow
nextFrameKeyCode: 'ArrowRight', // right-arrow
isEscape: 'Escape', // esc
inputCommentKeyCode: 'c',
};
chrome.storage.sync.get(settings, function(storage) {
settings.togglePlayAndPauseKeyCode = storage.togglePlayAndPauseKeyCode;
settings.jumpToBeginningKeyCode = storage.jumpToBeginningKeyCode;
settings.jumpToEndKeyCode = storage.jumpToEndKeyCode;
settings.prevFrameKeyCode = storage.prevFrameKeyCode;
settings.nextFrameKeyCode = storage.nextFrameKeyCode;
settings.volumeDownKeyCode = storage.volumeDownKeyCode;
settings.volumeUpKeyCode = storage.volumeUpKeyCode;
settings.toggleMuteKeyCode = storage.toggleMuteKeyCode;
settings.toggleRepeatPlayKeyCode = storage.toggleRepeatPlayKeyCode;
settings.toggleViewCommentKeyCode = storage.toggleViewCommentKeyCode;
settings.jumpToSpecifiedFrameKeyCode = storage.jumpToSpecifiedFrameKeyCode;
settings.backToBeforeFrameKeyCode = storage.backToBeforeFrameKeyCode;
settings.changeScreenModeKeyCode = storage.changeScreenModeKeyCode;
settings.onbeforeunloadWarning = Boolean(storage.onbeforeunloadWarning);
settings.checkCommandAvailability = Boolean(storage.checkCommandAvailability);
settings.scrollToPlayerKeyCode = Boolean(storage.scrollToPlayerKeyCode);
generateCheckCommandAvailabilityMessages();
});
// スタートやエンドに移動してしまったときの前の位置を保持する
var back = null;
// c を押したときにプレイヤーへのフォーカスを許す
var allowFocusPlayer = false;
// プレイヤーにフォーカス中にもう一度 c を押したときに有効になり
// 有効化されている間はescを押したりクリックしたりしない限りは
// 一切のコマンドが無効化される
var commentable = false;
// c を押すことでプレイヤーへのフォーカスが許可されたかどうか
// マウス操作によるプレイヤーへのフォーカスと区別するために使用する
var allowFocusPlayerFromKey = false;
var commandInitial;
var enabled;
var disabled;
function generateCheckCommandAvailabilityMessages() {
// if -> 簡略化表示, else -> 正式表示
if (settings.checkCommandAvailability === true) {
commandInitial = '<div id="niconicommander-status"><strong><span style="color:blue;">有効</span></strong></div>';
commandEnabled = '<strong><span style="color:blue;">有効</span></strong>';
commandDisabled = '<strong><span style="color:red;">無効</span></strong>';
}
else {
commandInitial = '<div id="niconicommander-status"><strong>niconicommander: <span style="color:blue;">有効</span></strong></div>';
commandEnabled = '<strong>niconicommander: <span style="color:blue;">有効</span></strong>';
commandDisabled = '<strong>niconicommander: <span style="color:red;">無効</span></strong><br>有効にするにはescを押すかプレイヤー以外をクリック';
}
// コマンドの有効/無効メッセージを表示する(最初は有効で表示)
$('body').append(commandInitial);
$('#niconicommander-status').css('top', $(window).scrollTop());
}
// メッセージを追従させる
$(document).scroll(function() {
$('#niconicommander-status').css('top', $(this).scrollTop());
});
// ページを離れるときに警告
window.onbeforeunload = function() {
if (settings.onbeforeunloadWarning === true) {
return '動画の読み込みがリセットされる可能性があります';
}
}
// クリックすると無効化されているコマンドが有効化される
$('body').on('click', function() {
allowFocusPlayer = false;
commentable = false;
allowFocusPlayerFromKey = false;
checkCommandAvailability();
});
// 動画プレイヤーにマウス移動した(クリックした、フォーカスしたこととみなす)ときに
// c を一回だけ押している状態だったらすべて解除する
$('#external_nicoplayer').on('mousemove', function() {
if (allowFocusPlayer === true && commentable === false && allowFocusPlayerFromKey === true) {
allowFocusPlayer = false;
commentable = false;
allowFocusPlayerFromKey = false;
checkCommandAvailability();
}
else {
return false;
}
});
// ショートカットキーに応じて関数を呼び出す
window.addEventListener('keydown', function(event) {
// 円マークをバックスラッシュに変換
var eventKey = encodeYenSignToBackslash(event.key);
// escが押されたらコマンドが有効化されアクティブフォーカスが外れる
if (eventKey == fixed.isEscape) {
allowFocusPlayer = false;
commentable = false;
allowFocusPlayerFromKey = false;
activeBlur();
checkCommandAvailability();
}
// 装飾キーをエスケープ
if (event.metaKey || event.ctrlKey || event.altKey) {
return false;
}
// コメント入力中はコマンドを無効化する
if (commentable === true) {
return false;
}
// c が一回押された状態(プレイヤーにファーカスしている状態)でもう一度 c を押すと
// コメント可能状態となり一切のコマンドが無効化される
// ただし c が一回押された状態で別のキーを押すと
// プレイヤーへのフォーカスが外れて状態がリセットされる
// プレイヤーにフォーカスしている状態やコメント入力状態でも
// escを押したりクリックしたりすると状態は解除されコマンドが有効化される(別記)
if (allowFocusPlayer === true && eventKey == fixed.inputCommentKeyCode) {
commentable = true;
allowFocusPlayerFromKey = false;
checkCommandAvailability();
return false;
}
else if (allowFocusPlayer === true && eventKey != fixed.inputCommentKeyCode) {
allowFocusPlayer = false;
commentable = false;
allowFocusPlayerFromKey = false;
checkCommandAvailability();
}
// 入力フォームにフォーカスがあるときはショートカットを無効化
if ((document.activeElement.nodeName === 'INPUT'
|| document.activeElement.nodeName == 'TEXTAREA'
|| document.activeElement.getAttribute('type') === 'text')
|| document.activeElement.isContentEditable === true) {
return false;
}
else if (allowFocusPlayer === false) {
activeBlur();
}
// オプションのキーと固定のキーに関しては
// 元々サイトで実装されているイベントリスナーを
// 無効化してこちらの処理のみを実行する
Object.keys(settings).forEach(function(key) {
if (eventKey == settings[key]) {
event.stopPropagation();
if (settings.scrollToPlayerKeyCode === true) {
scrollToPlayer();
}
}
});
Object.keys(fixed).forEach(function(key) {
if (eventKey == fixed[key]) {
event.stopPropagation();
if (settings.scrollToPlayerKeyCode === true) {
scrollToPlayer();
}
}
});
// ショートカットキーから関数を呼び出す
switch (eventKey) {
// オプションのキーコード
case settings.togglePlayAndPauseKeyCode: togglePlayAndPause(); break; // default: k
case settings.jumpToBeginningKeyCode: jumpToBeginning(); break; // default: h
case settings.jumpToEndKeyCode: jumpToEnd(); break; // default: e
case settings.prevFrameKeyCode: prevFrame(); break; // default: j
case settings.nextFrameKeyCode: nextFrame(); break; // default: l
case settings.volumeDownKeyCode: volumeDown(); break; // default: d
case settings.volumeUpKeyCode: volumeUp(); break; // default: u
case settings.toggleMuteKeyCode: toggleMute(); break; // default: m
case settings.toggleRepeatPlayKeyCode: toggleRepeatPlay(); break; // default: r
case settings.toggleViewCommentKeyCode: toggleViewComment(); break; // default: v
case settings.jumpToSpecifiedFrameKeyCode: jumpToSpecifiedFrame(); break; // default: t
case settings.backToBeforeFrameKeyCode: backToBeforeFrame(); break; // default: b
case settings.changeScreenModeKeyCode: changeScreenMode(); break; // default: s
// 固定のキーコード
case fixed.togglePlayAndPauseKeyCode: event.preventDefault(); togglePlayAndPause(); break; // space
case fixed.prevFrameKeyCode: event.preventDefault(); prevFrame(); break; // left-arrow
case fixed.nextFrameKeyCode: event.preventDefault(); nextFrame(); break; // right-arrow
case fixed.isEscape: event.preventDefault(); releaseFullScreenMode(); break; // esc
case fixed.inputCommentKeyCode: allowFocusPlayer = true; inputComment(); checkCommandAvailability(); break; // c
}
// 数字のキーを押すとその数字に対応する割合まで動画を移動する
// キーボードの 3 を押すと動画全体の 30% の位置に移動する
// 固定のキーコード
if (eventKey >= '0' && eventKey <= '9') {
jumpToTimerRatio(eventKey);
}
}, true);
// 現在コマンドが有効なのか無効なのかを表示する
function checkCommandAvailability() {
if (allowFocusPlayer === true && commentable === true) {
$('#niconicommander-status').html(commandDisabled);
}
else {
$('#niconicommander-status').html(commandEnabled);
}
}
// 再生/停止
function togglePlayAndPause() {
var player = document.getElementById('external_nicoplayer');
status = player.ext_getStatus();
if (status == 'playing')
player.ext_play(false);
else if (status == 'paused')
player.ext_play(true);
}
// 次のフレームに移動
function nextFrame() {
var player = document.getElementById('external_nicoplayer');
var totalTime = player.ext_getTotalTime();
var loadedRatio = player.ext_getLoadedRatio();
var loadedTime = totalTime * loadedRatio;
var player = document.getElementById('external_nicoplayer');
var playtime = player.ext_getPlayheadTime();
var next = playtime + 1;
player.ext_setPlayheadTime(next);
var checkNext = player.ext_getPlayheadTime();
while (playtime >= checkNext && next < loadedTime) {
next = next + 1;
player.ext_setPlayheadTime(next);
checkNext = player.ext_getPlayheadTime();
}
}
// 前のフレームに移動
function prevFrame() {
var player = document.getElementById('external_nicoplayer');
var playtime = player.ext_getPlayheadTime();
var prev = playtime - 1;
player.ext_setPlayheadTime(prev);
var checkPrev = player.ext_getPlayheadTime();
while (playtime <= checkPrev && prev > 0) {
prev = prev - 1;
player.ext_setPlayheadTime(prev);
checkPrev = player.ext_getPlayheadTime();
}
}
// 動画の一番最初に移動
function jumpToBeginning() {
var player = document.getElementById('external_nicoplayer');
back = player.ext_getPlayheadTime();
player.ext_setPlayheadTime(0);
}
// 動画の一番最後に移動
function jumpToEnd() {
var player = document.getElementById('external_nicoplayer');
back = player.ext_getPlayheadTime();
var endtime = player.ext_getTotalTime();
player.ext_setPlayheadTime(endtime);
}
// スタートやエンドに戻ってしまったときに元の位置に移動
function backToBeforeFrame() {
if (back !== null) {
var player = document.getElementById('external_nicoplayer');
player.ext_setPlayheadTime(back);
}
}
// 数字に対応する割合まで動画を移動
function jumpToTimerRatio(timerRatio) {
var player = document.getElementById('external_nicoplayer');
timerRatio = Number(timerRatio) / 10;
timerRatio = player.ext_getTotalTime() * timerRatio;
player.ext_setPlayheadTime(timerRatio);
}
// フルスクリーンモード/解除
function changeScreenMode() {
var player = document.getElementById('external_nicoplayer');
var fullScreen = player.ext_getVideoSize();
if (fullScreen == 'normal') {
player.ext_setVideoSize('fit');
} else if (fullScreen == 'fit') {
player.ext_setVideoSize('normal');
} else {
return false;
}
}
// フルスクリーンモード解除(esc専用)
function releaseFullScreenMode() {
var player = document.getElementById('external_nicoplayer');
var fullScreenStatus = player.ext_getVideoSize();
if (fullScreenStatus == 'fit') {
player.ext_setVideoSize('normal');
}
else {
return false;
}
}
// 時間を指定して移動
function jumpToSpecifiedFrame() {
inputOfJumpTo = prompt('移動先時間(形式: 25:25):');
if (inputOfJumpTo.match(/^\d{1,3}:\d{1,2}$/)) {
inputOfJumpTo = inputOfJumpTo.split(':');
inputOfJumpTo[0] = Number(inputOfJumpTo[0]);
inputOfJumpTo[1] = Number(inputOfJumpTo[1]);
if (inputOfJumpTo[1] >= 60)
alert('秒数は60秒以内で入力してください');
else {
var jumpTo;
jumpTo = inputOfJumpTo[0] * 60;
jumpTo = jumpTo + inputOfJumpTo[1] + 1;
var player = document.getElementById('external_nicoplayer');
var totalTime = player.ext_getTotalTime();
if (jumpTo <= totalTime)
player.ext_setPlayheadTime(jumpTo);
else
alert('指定された時間は動画の再生時間を超えています');
}
}
else
alert('正しい形式で入力してください(例 25:25)');
}
// 音量ダウン
function volumeDown() {
var player = document.getElementById('external_nicoplayer');
var volume = Number(player.ext_getVolume());
if (volume >= 0) {
player.ext_setVolume(volume - 10);
}
}
// 音量アップ
function volumeUp() {
var player = document.getElementById('external_nicoplayer');
var volume = Number(player.ext_getVolume());
if (volume <= 100) {
player.ext_setVolume(volume + 10);
}
}
// ミュート/解除
function toggleMute() {
var player = document.getElementById('external_nicoplayer');
if (player.ext_isMute() === true) {
player.ext_setMute(false);
}
else {
player.ext_setMute(true);
}
}
// リピート再生
function toggleRepeatPlay() {
var player = document.getElementById('external_nicoplayer');
if (player.ext_isRepeat() === true) {
player.ext_setRepeat(false);
}
else {
player.ext_setRepeat(true);
}
}
// コメント表示/非表示
function toggleViewComment() {
var player = document.getElementById('external_nicoplayer');
if (player.ext_isCommentVisible() === true) {
player.ext_setCommentVisible(false);
}
else {
player.ext_setCommentVisible(true);
}
}
// コメント入力欄にフォーカス
function inputComment() {
allowFocusPlayerFromKey = true;
document.getElementById('external_nicoplayer').focus();
checkCommandAvailability();
}
// 円マークをバックスラッシュに変換する
function encodeYenSignToBackslash(key) {
// 165 -> Yen Sign
if (key.charCodeAt() == 165) {
key = '\\';
}
return key;
}
// 動画プレイヤーまでスクロール
function scrollToPlayer() {
var player = document.getElementById('external_nicoplayer');
var rect = player.getBoundingClientRect();
var positionY = rect.top;
var dElm = document.documentElement;
var dBody = document.body;
var scrollY = dElm.scrollTop || dBody.scrollTop;
var y = positionY + scrollY - 100;
scrollTo(0, y);
}
// アクティブフォーカスを外す
function activeBlur() {
document.activeElement.blur();
}
// 常にプレイヤーからフォーカスを外す
$('#external_nicoplayer').on('focus', function() {
if (allowFocusPlayer === false) {
$(this).blur();
}
else {
return false;
}
});
});