retold-remote 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -232,6 +232,85 @@ const _ViewConfiguration =
232
232
  background: var(--retold-accent);
233
233
  color: var(--retold-bg-primary);
234
234
  }
235
+ /* Video action menu */
236
+ .retold-remote-video-action-menu
237
+ {
238
+ display: flex;
239
+ flex-direction: column;
240
+ align-items: center;
241
+ justify-content: center;
242
+ gap: 12px;
243
+ width: 100%;
244
+ height: 100%;
245
+ }
246
+ .retold-remote-video-action-menu-title
247
+ {
248
+ font-size: 0.85rem;
249
+ color: var(--retold-text-secondary);
250
+ margin-bottom: 4px;
251
+ text-align: center;
252
+ overflow: hidden;
253
+ text-overflow: ellipsis;
254
+ max-width: 80%;
255
+ }
256
+ .retold-remote-video-action-thumb-wrap
257
+ {
258
+ margin-bottom: 4px;
259
+ text-align: center;
260
+ }
261
+ .retold-remote-video-action-thumb-wrap img
262
+ {
263
+ max-width: 640px;
264
+ max-height: 360px;
265
+ border-radius: 6px;
266
+ border: 1px solid var(--retold-border);
267
+ object-fit: contain;
268
+ background: var(--retold-bg-primary);
269
+ }
270
+ .retold-remote-video-action-thumb-wrap .retold-remote-video-action-thumb-loading
271
+ {
272
+ color: var(--retold-text-dim);
273
+ font-size: 0.78rem;
274
+ font-style: italic;
275
+ padding: 8px;
276
+ }
277
+ .retold-remote-video-action-btn
278
+ {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 12px;
282
+ padding: 12px 24px;
283
+ min-width: 280px;
284
+ border: 1px solid var(--retold-border);
285
+ border-radius: 6px;
286
+ background: var(--retold-bg-secondary);
287
+ color: var(--retold-text-secondary);
288
+ font-size: 0.85rem;
289
+ cursor: pointer;
290
+ transition: border-color 0.15s, color 0.15s, background 0.15s;
291
+ font-family: inherit;
292
+ text-align: left;
293
+ }
294
+ .retold-remote-video-action-btn:hover,
295
+ .retold-remote-video-action-btn.selected
296
+ {
297
+ border-color: var(--retold-accent);
298
+ color: var(--retold-text-primary);
299
+ background: var(--retold-bg-tertiary);
300
+ }
301
+ .retold-remote-video-action-key
302
+ {
303
+ display: inline-block;
304
+ padding: 2px 8px;
305
+ border: 1px solid var(--retold-border);
306
+ border-radius: 3px;
307
+ background: var(--retold-bg-primary);
308
+ color: var(--retold-text-dim);
309
+ font-size: 0.72rem;
310
+ font-family: var(--retold-font-mono, monospace);
311
+ min-width: 24px;
312
+ text-align: center;
313
+ }
235
314
  /* Ebook reader */
236
315
  .retold-remote-ebook-wrap
237
316
  {
@@ -376,6 +455,7 @@ class RetoldRemoteMediaViewerView extends libPictView
376
455
  tmpRemote.ActiveMode = 'viewer';
377
456
  tmpRemote.CurrentViewerFile = pFilePath;
378
457
  tmpRemote.CurrentViewerMediaType = pMediaType;
458
+ tmpRemote.VideoMenuActive = (pMediaType === 'video');
379
459
 
380
460
  // Show viewer, hide gallery
381
461
  let tmpGalleryContainer = document.getElementById('RetoldRemote-Gallery-Container');
@@ -477,12 +557,85 @@ class RetoldRemoteMediaViewerView extends libPictView
477
557
 
478
558
  _buildVideoHTML(pURL, pFileName)
479
559
  {
560
+ let tmpCapabilities = this.pict.AppData.RetoldRemote.ServerCapabilities || {};
561
+ let tmpRemote = this.pict.AppData.RetoldRemote;
562
+ let tmpFilePath = tmpRemote.CurrentViewerFile;
563
+
564
+ // Build the action menu (shown by default instead of the player)
565
+ let tmpHTML = '<div class="retold-remote-video-action-menu" id="RetoldRemote-VideoActionMenu">';
566
+ tmpHTML += '<div class="retold-remote-video-action-menu-title">' + this._escapeHTML(pFileName) + '</div>';
567
+
568
+ // Frame preview container (loaded on demand via t key or automatically)
569
+ if (tmpCapabilities.ffmpeg)
570
+ {
571
+ tmpHTML += '<div id="RetoldRemote-VideoActionThumb" class="retold-remote-video-action-thumb-wrap"></div>';
572
+ // Kick off frame extraction automatically
573
+ setTimeout(() => { this.loadVideoMenuFrame(); }, 0);
574
+ }
575
+
576
+ // Explore option (e)
577
+ if (tmpCapabilities.ffmpeg)
578
+ {
579
+ tmpHTML += '<button class="retold-remote-video-action-btn" '
580
+ + 'onclick="pict.views[\'RetoldRemote-VideoExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '
581
+ + 'title="Explore frames from this video">'
582
+ + '<span class="retold-remote-video-action-key">e</span>'
583
+ + 'Explore Video Frames'
584
+ + '</button>';
585
+ }
586
+
587
+ // Play option (space/enter)
588
+ tmpHTML += '<button class="retold-remote-video-action-btn selected" '
589
+ + 'onclick="pict.views[\'RetoldRemote-MediaViewer\'].playVideo()" '
590
+ + 'title="Play video in browser">'
591
+ + '<span class="retold-remote-video-action-key">Space</span>'
592
+ + 'Play in Browser'
593
+ + '</button>';
594
+
595
+ // Thumbnail option (t)
596
+ if (tmpCapabilities.ffmpeg)
597
+ {
598
+ tmpHTML += '<button class="retold-remote-video-action-btn" '
599
+ + 'onclick="pict.views[\'RetoldRemote-MediaViewer\'].loadVideoMenuFrame()" '
600
+ + 'title="Extract a frame from the midpoint of this video">'
601
+ + '<span class="retold-remote-video-action-key">t</span>'
602
+ + 'Thumbnail'
603
+ + '</button>';
604
+ }
605
+
606
+ // VLC streaming option (v) — always available, streams from server to client VLC
607
+ tmpHTML += '<button class="retold-remote-video-action-btn" '
608
+ + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '
609
+ + 'title="Stream to VLC on this device">'
610
+ + '<span class="retold-remote-video-action-key">v</span>'
611
+ + 'Stream with VLC'
612
+ + '</button>';
613
+
614
+ tmpHTML += '</div>';
615
+
616
+ return tmpHTML;
617
+ }
618
+
619
+ /**
620
+ * Launch the in-browser video player (from the video action menu).
621
+ */
622
+ playVideo()
623
+ {
624
+ let tmpRemote = this.pict.AppData.RetoldRemote;
625
+ let tmpFilePath = tmpRemote.CurrentViewerFile;
626
+ if (!tmpFilePath) return;
627
+
628
+ let tmpFileName = tmpFilePath.replace(/^.*\//, '');
629
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
630
+ let tmpContentURL = tmpProvider ? tmpProvider.getContentURL(tmpFilePath) : ('/content/' + encodeURIComponent(tmpFilePath));
631
+ let tmpCapabilities = tmpRemote.ServerCapabilities || {};
632
+
480
633
  let tmpHTML = '<div class="retold-remote-video-wrap">';
481
634
 
482
- let tmpAutoplayVideo = this.pict.AppData.RetoldRemote.AutoplayVideo ? ' autoplay' : '';
635
+ let tmpAutoplayVideo = tmpRemote.AutoplayVideo ? ' autoplay' : '';
483
636
  tmpHTML += '<video controls' + tmpAutoplayVideo + ' preload="metadata" '
484
637
  + 'id="RetoldRemote-VideoPlayer">'
485
- + '<source src="' + pURL + '">'
638
+ + '<source src="' + tmpContentURL + '">'
486
639
  + 'Your browser does not support the video tag.'
487
640
  + '</video>';
488
641
 
@@ -490,31 +643,79 @@ class RetoldRemoteMediaViewerView extends libPictView
490
643
  tmpHTML += '<div class="retold-remote-video-stats" id="RetoldRemote-VideoStats">';
491
644
  tmpHTML += '<span class="retold-remote-video-stat-label">Loading info...</span>';
492
645
 
493
- // Explore Video button (only when ffmpeg is available)
494
- let tmpCapabilities = this.pict.AppData.RetoldRemote.ServerCapabilities || {};
495
646
  if (tmpCapabilities.ffmpeg)
496
647
  {
497
648
  tmpHTML += '<button class="retold-remote-explore-btn" '
498
649
  + 'onclick="pict.views[\'RetoldRemote-VideoExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '
499
650
  + 'title="Explore frames from this video">'
500
- + '&#128270; Explore Video'
651
+ + 'Explore Video'
501
652
  + '</button>';
502
653
  }
503
654
 
504
- // VLC button (only shown when VLC capability is available)
505
- if (tmpCapabilities.vlc)
506
- {
507
- tmpHTML += '<button class="retold-remote-vlc-btn" '
508
- + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._openWithVLC()" '
509
- + 'title="Open with VLC (Enter)">'
510
- + '&#9654; Open ' + this._escapeHTML(pFileName) + ' with VLC'
511
- + '</button>';
512
- }
655
+ tmpHTML += '<button class="retold-remote-vlc-btn" '
656
+ + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '
657
+ + 'title="Stream to VLC on this device">'
658
+ + 'Stream with VLC'
659
+ + '</button>';
513
660
 
514
661
  tmpHTML += '</div>'; // end stats
515
662
  tmpHTML += '</div>'; // end wrap
516
663
 
517
- return tmpHTML;
664
+ // Replace the action menu with the player
665
+ let tmpMenu = document.getElementById('RetoldRemote-VideoActionMenu');
666
+ if (tmpMenu)
667
+ {
668
+ tmpMenu.outerHTML = tmpHTML;
669
+ }
670
+
671
+ // Mark that we are now in player mode (not menu mode)
672
+ tmpRemote.VideoMenuActive = false;
673
+ }
674
+
675
+ /**
676
+ * Extract and display a single full-resolution frame from the midpoint of the current video.
677
+ */
678
+ loadVideoMenuFrame()
679
+ {
680
+ let tmpRemote = this.pict.AppData.RetoldRemote;
681
+ let tmpFilePath = tmpRemote.CurrentViewerFile;
682
+ if (!tmpFilePath) return;
683
+
684
+ let tmpThumbWrap = document.getElementById('RetoldRemote-VideoActionThumb');
685
+ if (!tmpThumbWrap) return;
686
+
687
+ tmpThumbWrap.innerHTML = '<div class="retold-remote-video-action-thumb-loading">Extracting frame...</div>';
688
+
689
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
690
+ let tmpPathParam = tmpProvider ? tmpProvider._getPathParam(tmpFilePath) : encodeURIComponent(tmpFilePath);
691
+
692
+ fetch('/api/media/video-frames?path=' + tmpPathParam + '&count=1')
693
+ .then((pResponse) => pResponse.json())
694
+ .then((pData) =>
695
+ {
696
+ // Verify we are still on the same file and still in the menu
697
+ if (tmpRemote.CurrentViewerFile !== tmpFilePath) return;
698
+ let tmpWrap = document.getElementById('RetoldRemote-VideoActionThumb');
699
+ if (!tmpWrap) return;
700
+
701
+ if (pData && pData.Frames && pData.Frames.length > 0)
702
+ {
703
+ let tmpFrame = pData.Frames[0];
704
+ let tmpFrameURL = '/api/media/video-frame/' + pData.CacheKey + '/' + tmpFrame.Filename;
705
+ tmpWrap.innerHTML = '<img src="' + tmpFrameURL + '" '
706
+ + 'alt="' + this._escapeHTML(tmpFilePath.replace(/^.*\//, '')) + '" '
707
+ + 'onerror="this.parentNode.innerHTML=\'\'">';
708
+ }
709
+ else
710
+ {
711
+ tmpWrap.innerHTML = '';
712
+ }
713
+ })
714
+ .catch(() =>
715
+ {
716
+ let tmpWrap = document.getElementById('RetoldRemote-VideoActionThumb');
717
+ if (tmpWrap) tmpWrap.innerHTML = '';
718
+ });
518
719
  }
519
720
 
520
721
  _buildAudioHTML(pURL, pFileName)
@@ -530,19 +731,29 @@ class RetoldRemoteMediaViewerView extends libPictView
530
731
  + 'Your browser does not support the audio tag.'
531
732
  + '</audio>';
532
733
 
734
+ // Action buttons below the player
735
+ tmpHTML += '<div style="margin-top: 20px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">';
736
+
533
737
  // Explore Audio button (available when ffprobe is present)
534
738
  let tmpCapabilities = this.pict.AppData.RetoldRemote.ServerCapabilities || {};
535
739
  if (tmpCapabilities.ffprobe || tmpCapabilities.ffmpeg)
536
740
  {
537
- tmpHTML += '<div style="margin-top: 20px;">'
538
- + '<button class="retold-remote-explore-btn" '
741
+ tmpHTML += '<button class="retold-remote-explore-btn" '
539
742
  + 'onclick="pict.views[\'RetoldRemote-AudioExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '
540
743
  + 'title="Explore waveform and extract segments from this audio">'
541
- + '&#128202; Explore Audio'
542
- + '</button>'
543
- + '</div>';
744
+ + 'Explore Audio'
745
+ + '</button>';
544
746
  }
545
747
 
748
+ // Stream with VLC
749
+ tmpHTML += '<button class="retold-remote-vlc-btn" '
750
+ + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" '
751
+ + 'title="Stream to VLC on this device (v)">'
752
+ + 'Stream with VLC'
753
+ + '</button>';
754
+
755
+ tmpHTML += '</div>';
756
+
546
757
  tmpHTML += '</div>';
547
758
  return tmpHTML;
548
759
  }
@@ -77,6 +77,64 @@ const _ViewConfiguration =
77
77
  {
78
78
  color: var(--retold-danger-muted);
79
79
  }
80
+ .retold-remote-settings-vlc-btn
81
+ {
82
+ display: block;
83
+ width: 100%;
84
+ padding: 8px 12px;
85
+ border: 1px solid var(--retold-border);
86
+ border-radius: 4px;
87
+ background: var(--retold-bg-secondary);
88
+ color: var(--retold-text-secondary);
89
+ font-size: 0.75rem;
90
+ font-family: inherit;
91
+ cursor: pointer;
92
+ text-align: left;
93
+ transition: background 0.15s, color 0.15s;
94
+ }
95
+ .retold-remote-settings-vlc-btn:hover
96
+ {
97
+ background: var(--retold-bg-hover);
98
+ color: var(--retold-text-primary);
99
+ }
100
+ .retold-remote-settings-shortcut-group
101
+ {
102
+ margin-bottom: 10px;
103
+ }
104
+ .retold-remote-settings-shortcut-group-title
105
+ {
106
+ font-size: 0.68rem;
107
+ font-weight: 600;
108
+ color: var(--retold-text-muted);
109
+ margin-bottom: 4px;
110
+ padding-bottom: 2px;
111
+ border-bottom: 1px solid var(--retold-border);
112
+ }
113
+ .retold-remote-settings-shortcut-row
114
+ {
115
+ display: flex;
116
+ justify-content: space-between;
117
+ align-items: center;
118
+ padding: 2px 0;
119
+ }
120
+ .retold-remote-settings-shortcut-desc
121
+ {
122
+ color: var(--retold-text-dim);
123
+ font-size: 0.72rem;
124
+ }
125
+ .retold-remote-settings-shortcut-key
126
+ {
127
+ display: inline-block;
128
+ padding: 1px 6px;
129
+ border: 1px solid var(--retold-border);
130
+ border-radius: 3px;
131
+ background: var(--retold-bg-primary);
132
+ color: var(--retold-accent);
133
+ font-size: 0.68rem;
134
+ font-family: var(--retold-font-mono, monospace);
135
+ min-width: 18px;
136
+ text-align: center;
137
+ }
80
138
  `
81
139
  };
82
140
 
@@ -237,31 +295,92 @@ class RetoldRemoteSettingsPanelView extends libPictView
237
295
  tmpHTML += '</div>';
238
296
  tmpHTML += '</div>'; // end capabilities section
239
297
 
298
+ // VLC Setup
299
+ tmpHTML += '<div class="retold-remote-settings-section">';
300
+ tmpHTML += '<div class="retold-remote-settings-section-title">VLC Streaming</div>';
301
+ tmpHTML += '<button class="retold-remote-settings-vlc-btn" onclick="pict.views[\'RetoldRemote-VLCSetup\'].openModal()">';
302
+ tmpHTML += 'VLC Protocol Setup';
303
+ tmpHTML += '</button>';
304
+ tmpHTML += '</div>';
305
+
240
306
  // Keyboard shortcuts
241
307
  tmpHTML += '<div class="retold-remote-settings-section">';
242
308
  tmpHTML += '<div class="retold-remote-settings-section-title">Keyboard Shortcuts</div>';
243
309
 
244
- let tmpShortcuts = [
245
- { key: 'Arrow keys', desc: 'Navigate gallery' },
310
+ tmpHTML += this._buildShortcutGroup('Global',
311
+ [
312
+ { key: 'F1', desc: 'Help panel' },
313
+ { key: 'F9', desc: 'Focus sidebar' },
314
+ { key: '/', desc: 'Search / filter bar' },
315
+ { key: 'Esc', desc: 'Close overlay / back' }
316
+ ]);
317
+
318
+ tmpHTML += this._buildShortcutGroup('Gallery',
319
+ [
320
+ { key: '\u2190 \u2191 \u2192 \u2193', desc: 'Navigate items' },
246
321
  { key: 'Enter', desc: 'Open item' },
247
- { key: 'Escape', desc: 'Back / close' },
248
- { key: 'j / k', desc: 'Next / prev in viewer' },
249
- { key: 'f', desc: 'Fullscreen' },
250
- { key: 'i', desc: 'File info' },
322
+ { key: '1 2 3 4', desc: 'Open as image / video / audio / text' },
323
+ { key: 'Esc', desc: 'Go up one folder' },
324
+ { key: 'Home', desc: 'Jump to first item' },
325
+ { key: 'End', desc: 'Jump to last item' },
326
+ { key: 'g', desc: 'Toggle grid / list' },
327
+ { key: 'f', desc: 'Advanced filter panel' },
328
+ { key: 's', desc: 'Focus sort dropdown' },
329
+ { key: 'x', desc: 'Clear all filters' },
330
+ { key: 'c', desc: 'Settings panel' },
331
+ { key: 'd', desc: 'Distraction-free mode' }
332
+ ]);
333
+
334
+ tmpHTML += this._buildShortcutGroup('Sidebar (F9)',
335
+ [
336
+ { key: '\u2191 \u2193', desc: 'Navigate file list' },
337
+ { key: 'Home', desc: 'Jump to first' },
338
+ { key: 'End', desc: 'Jump to last' },
339
+ { key: 'Enter', desc: 'Open item' },
340
+ { key: 'Esc', desc: 'Return to gallery' }
341
+ ]);
342
+
343
+ tmpHTML += this._buildShortcutGroup('Media Viewer',
344
+ [
345
+ { key: 'Esc', desc: 'Back to gallery' },
346
+ { key: '\u2192 j', desc: 'Next file' },
347
+ { key: '\u2190 k', desc: 'Previous file' },
348
+ { key: '1 2 3 4', desc: 'View as image / video / audio / text' },
251
349
  { key: 'Space', desc: 'Play / pause' },
252
- { key: '+ / -', desc: 'Zoom in / out' },
350
+ { key: 'f', desc: 'Fullscreen' },
351
+ { key: 'i', desc: 'File info overlay' },
352
+ { key: 'v', desc: 'Stream with VLC' },
353
+ { key: '+ -', desc: 'Zoom in / out' },
253
354
  { key: '0', desc: 'Reset zoom' },
254
- { key: 'g', desc: 'Toggle grid / list' },
255
- { key: '/', desc: 'Focus search' }
256
- ];
257
-
258
- for (let i = 0; i < tmpShortcuts.length; i++)
259
- {
260
- tmpHTML += '<div class="retold-remote-settings-cap-row">';
261
- tmpHTML += '<span class="retold-remote-settings-cap-label">' + tmpShortcuts[i].desc + '</span>';
262
- tmpHTML += '<span style="color: var(--retold-accent); font-family: var(--retold-font-mono, monospace);">' + tmpShortcuts[i].key + '</span>';
263
- tmpHTML += '</div>';
264
- }
355
+ { key: 'z', desc: 'Cycle fit mode' },
356
+ { key: 'd', desc: 'Distraction-free mode' }
357
+ ]);
358
+
359
+ tmpHTML += this._buildShortcutGroup('Video Menu',
360
+ [
361
+ { key: 'Space', desc: 'Play in browser' },
362
+ { key: 'Enter', desc: 'Play in browser' },
363
+ { key: 'e', desc: 'Explore video frames' },
364
+ { key: 't', desc: 'Extract thumbnail' },
365
+ { key: 'v', desc: 'Stream with VLC' },
366
+ { key: '\u2192 j', desc: 'Next file' },
367
+ { key: '\u2190 k', desc: 'Previous file' },
368
+ { key: 'Esc', desc: 'Back to gallery' }
369
+ ]);
370
+
371
+ tmpHTML += this._buildShortcutGroup('Video Explorer',
372
+ [
373
+ { key: 'Esc', desc: 'Back' }
374
+ ]);
375
+
376
+ tmpHTML += this._buildShortcutGroup('Audio Explorer',
377
+ [
378
+ { key: 'Space', desc: 'Play selection' },
379
+ { key: '+ -', desc: 'Zoom in / out' },
380
+ { key: '0', desc: 'Zoom to fit' },
381
+ { key: 'z', desc: 'Zoom to selection' },
382
+ { key: 'Esc', desc: 'Clear selection / back' }
383
+ ]);
265
384
 
266
385
  tmpHTML += '</div>'; // end shortcuts section
267
386
 
@@ -270,6 +389,23 @@ class RetoldRemoteSettingsPanelView extends libPictView
270
389
  tmpContainer.innerHTML = tmpHTML;
271
390
  }
272
391
 
392
+ _buildShortcutGroup(pTitle, pShortcuts)
393
+ {
394
+ let tmpHTML = '<div class="retold-remote-settings-shortcut-group">';
395
+ tmpHTML += '<div class="retold-remote-settings-shortcut-group-title">' + pTitle + '</div>';
396
+
397
+ for (let i = 0; i < pShortcuts.length; i++)
398
+ {
399
+ tmpHTML += '<div class="retold-remote-settings-shortcut-row">';
400
+ tmpHTML += '<span class="retold-remote-settings-shortcut-desc">' + pShortcuts[i].desc + '</span>';
401
+ tmpHTML += '<span class="retold-remote-settings-shortcut-key">' + pShortcuts[i].key + '</span>';
402
+ tmpHTML += '</div>';
403
+ }
404
+
405
+ tmpHTML += '</div>';
406
+ return tmpHTML;
407
+ }
408
+
273
409
  changeTheme(pThemeKey)
274
410
  {
275
411
  let tmpThemeProvider = this.pict.providers['RetoldRemote-Theme'];