retold-remote 0.0.4 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retold-remote",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Retold Remote - NAS media browser with gallery views and keyboard navigation",
5
5
  "main": "source/Pict-RetoldRemote-Bundle.js",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "pict-section-filebrowser": "^0.0.2",
38
38
  "pict-service-commandlineutility": "^1.0.19",
39
39
  "pict-view": "^1.0.67",
40
- "retold-content-system": "^1.0.4",
40
+ "retold-content-system": "^1.0.8",
41
41
  "yauzl": "^3.2.0"
42
42
  },
43
43
  "optionalDependencies": {
@@ -20,6 +20,7 @@ const libViewMediaViewer = require('./views/PictView-Remote-MediaViewer.js');
20
20
  const libViewImageViewer = require('./views/PictView-Remote-ImageViewer.js');
21
21
  const libViewVideoExplorer = require('./views/PictView-Remote-VideoExplorer.js');
22
22
  const libViewAudioExplorer = require('./views/PictView-Remote-AudioExplorer.js');
23
+ const libViewVLCSetup = require('./views/PictView-Remote-VLCSetup.js');
23
24
 
24
25
  // Application configuration
25
26
  const _DefaultConfiguration = require('./Pict-Application-RetoldRemote-Configuration.json');
@@ -51,6 +52,7 @@ class RetoldRemoteApplication extends libContentEditorApplication
51
52
  this.pict.addView('RetoldRemote-SettingsPanel', libViewSettingsPanel.default_configuration, libViewSettingsPanel);
52
53
  this.pict.addView('RetoldRemote-VideoExplorer', libViewVideoExplorer.default_configuration, libViewVideoExplorer);
53
54
  this.pict.addView('RetoldRemote-AudioExplorer', libViewAudioExplorer.default_configuration, libViewAudioExplorer);
55
+ this.pict.addView('RetoldRemote-VLCSetup', libViewVLCSetup.default_configuration, libViewVLCSetup);
54
56
 
55
57
  // Add new providers
56
58
  this.pict.addProvider('RetoldRemote-Provider', libProviderRetoldRemote.default_configuration, libProviderRetoldRemote);
@@ -357,6 +359,44 @@ class RetoldRemoteApplication extends libContentEditorApplication
357
359
  }
358
360
  }
359
361
 
362
+ /**
363
+ * Navigate to a file with an explicit media type override, bypassing
364
+ * extension-based detection.
365
+ *
366
+ * @param {string} pFilePath - Relative file path
367
+ * @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
368
+ */
369
+ navigateToFileAs(pFilePath, pMediaType)
370
+ {
371
+ if (!pFilePath)
372
+ {
373
+ return;
374
+ }
375
+
376
+ let tmpRemote = this.pict.AppData.RetoldRemote;
377
+
378
+ // Update the hash
379
+ let tmpFragProvider = this.pict.providers['RetoldRemote-Provider'];
380
+ let tmpFragId = tmpFragProvider ? tmpFragProvider.getFragmentIdentifier(pFilePath) : pFilePath;
381
+ window.location.hash = '#/view/' + tmpFragId;
382
+
383
+ // Update parent state for compatibility
384
+ this.pict.AppData.ContentEditor.CurrentFile = pFilePath;
385
+ this.pict.AppData.ContentEditor.ActiveEditor = 'binary';
386
+
387
+ let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
388
+ if (tmpViewer)
389
+ {
390
+ tmpViewer.showMedia(pFilePath, pMediaType);
391
+ }
392
+
393
+ let tmpTopBar = this.pict.views['ContentEditor-TopBar'];
394
+ if (tmpTopBar)
395
+ {
396
+ tmpTopBar.updateInfo();
397
+ }
398
+ }
399
+
360
400
  /**
361
401
  * Override loadFileList to also populate the gallery and fetch folder summary.
362
402
  */
@@ -1,7 +1,7 @@
1
1
  const libPictProvider = require('pict-provider');
2
2
 
3
3
  const _ImageExtensions = { 'png': true, 'jpg': true, 'jpeg': true, 'gif': true, 'webp': true, 'svg': true, 'bmp': true, 'ico': true, 'avif': true, 'tiff': true, 'tif': true };
4
- const _VideoExtensions = { 'mp4': true, 'webm': true, 'mov': true, 'mkv': true, 'avi': true, 'wmv': true, 'flv': true, 'm4v': true };
4
+ const _VideoExtensions = { 'mp4': true, 'webm': true, 'mov': true, 'mkv': true, 'avi': true, 'wmv': true, 'flv': true, 'm4v': true, 'ogv': true, 'mpg': true, 'mpeg': true, 'mpe': true, 'mpv': true, 'm2v': true, 'ts': true, 'mts': true, 'm2ts': true, 'vob': true, '3gp': true, '3g2': true, 'f4v': true, 'rm': true, 'rmvb': true, 'divx': true, 'asf': true, 'mxf': true, 'dv': true, 'nsv': true, 'nuv': true, 'y4m': true, 'wtv': true, 'swf': true, 'dat': true };
5
5
  const _AudioExtensions = { 'mp3': true, 'wav': true, 'ogg': true, 'flac': true, 'aac': true, 'm4a': true, 'wma': true };
6
6
  const _DocumentExtensions = { 'pdf': true, 'epub': true, 'mobi': true };
7
7
 
@@ -234,6 +234,26 @@ class GalleryNavigationProvider extends libPictProvider
234
234
  this.moveCursor(tmpItems.length - 1);
235
235
  break;
236
236
 
237
+ case '1':
238
+ pEvent.preventDefault();
239
+ this.openCurrentAs('image');
240
+ break;
241
+
242
+ case '2':
243
+ pEvent.preventDefault();
244
+ this.openCurrentAs('video');
245
+ break;
246
+
247
+ case '3':
248
+ pEvent.preventDefault();
249
+ this.openCurrentAs('audio');
250
+ break;
251
+
252
+ case '4':
253
+ pEvent.preventDefault();
254
+ this.openCurrentAs('text');
255
+ break;
256
+
237
257
  case 'f':
238
258
  pEvent.preventDefault();
239
259
  {
@@ -469,7 +489,7 @@ class GalleryNavigationProvider extends libPictProvider
469
489
 
470
490
  case 'v':
471
491
  pEvent.preventDefault();
472
- this._openWithVLC();
492
+ this._streamWithVLC();
473
493
  return;
474
494
  }
475
495
  return;
@@ -532,13 +552,38 @@ class GalleryNavigationProvider extends libPictProvider
532
552
 
533
553
  case 'Enter':
534
554
  pEvent.preventDefault();
535
- this._openWithVLC();
555
+ this._streamWithVLC();
556
+ break;
557
+
558
+ case 'v':
559
+ pEvent.preventDefault();
560
+ this._streamWithVLC();
536
561
  break;
537
562
 
538
563
  case 'd':
539
564
  pEvent.preventDefault();
540
565
  this._toggleDistractionFree();
541
566
  break;
567
+
568
+ case '1':
569
+ pEvent.preventDefault();
570
+ this.switchViewerType('image');
571
+ break;
572
+
573
+ case '2':
574
+ pEvent.preventDefault();
575
+ this.switchViewerType('video');
576
+ break;
577
+
578
+ case '3':
579
+ pEvent.preventDefault();
580
+ this.switchViewerType('audio');
581
+ break;
582
+
583
+ case '4':
584
+ pEvent.preventDefault();
585
+ this.switchViewerType('text');
586
+ break;
542
587
  }
543
588
  }
544
589
 
@@ -683,6 +728,59 @@ class GalleryNavigationProvider extends libPictProvider
683
728
  }
684
729
  }
685
730
 
731
+ /**
732
+ * Open the currently selected gallery item, forcing a specific media type
733
+ * regardless of its file extension.
734
+ *
735
+ * @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
736
+ */
737
+ openCurrentAs(pMediaType)
738
+ {
739
+ let tmpRemote = this.pict.AppData.RetoldRemote;
740
+ let tmpItems = tmpRemote.GalleryItems || [];
741
+ let tmpIndex = tmpRemote.GalleryCursorIndex || 0;
742
+
743
+ if (tmpIndex >= tmpItems.length)
744
+ {
745
+ return;
746
+ }
747
+
748
+ let tmpItem = tmpItems[tmpIndex];
749
+
750
+ if (tmpItem.Type === 'folder' || tmpItem.Type === 'archive')
751
+ {
752
+ return;
753
+ }
754
+
755
+ let tmpApp = this.pict.PictApplication;
756
+ if (tmpApp && tmpApp.navigateToFileAs)
757
+ {
758
+ tmpApp.navigateToFileAs(tmpItem.Path, pMediaType);
759
+ }
760
+ }
761
+
762
+ /**
763
+ * Re-open the currently viewed file with a different media type.
764
+ *
765
+ * @param {string} pMediaType - 'image', 'video', 'audio', or 'text'
766
+ */
767
+ switchViewerType(pMediaType)
768
+ {
769
+ let tmpRemote = this.pict.AppData.RetoldRemote;
770
+ let tmpFilePath = tmpRemote.CurrentViewerFile;
771
+
772
+ if (!tmpFilePath)
773
+ {
774
+ return;
775
+ }
776
+
777
+ let tmpViewer = this.pict.views['RetoldRemote-MediaViewer'];
778
+ if (tmpViewer)
779
+ {
780
+ tmpViewer.showMedia(tmpFilePath, pMediaType);
781
+ }
782
+ }
783
+
686
784
  /**
687
785
  * Navigate up one directory level.
688
786
  */
@@ -1344,6 +1442,47 @@ class GalleryNavigationProvider extends libPictProvider
1344
1442
  });
1345
1443
  }
1346
1444
 
1445
+ /**
1446
+ * Stream the current media file to VLC on the client device via vlc:// protocol link.
1447
+ */
1448
+ _streamWithVLC()
1449
+ {
1450
+ let tmpRemote = this.pict.AppData.RetoldRemote;
1451
+ let tmpMediaType = tmpRemote.CurrentViewerMediaType;
1452
+
1453
+ if (tmpMediaType !== 'video' && tmpMediaType !== 'audio')
1454
+ {
1455
+ return;
1456
+ }
1457
+
1458
+ let tmpFilePath = tmpRemote.CurrentViewerFile;
1459
+ if (!tmpFilePath)
1460
+ {
1461
+ return;
1462
+ }
1463
+
1464
+ let tmpProvider = this.pict.providers['RetoldRemote-Provider'];
1465
+ let tmpContentPath = tmpProvider ? tmpProvider.getContentURL(tmpFilePath) : ('/content/' + encodeURIComponent(tmpFilePath));
1466
+ let tmpStreamURL = window.location.origin + tmpContentPath;
1467
+ // On Windows, VLC's native handler expects the raw URL.
1468
+ // On macOS/Linux our custom handlers URL-decode, so we encode.
1469
+ let tmpIsWindows = /Windows/.test(navigator.userAgent);
1470
+ let tmpVLCURL = tmpIsWindows
1471
+ ? ('vlc://' + tmpStreamURL)
1472
+ : ('vlc://' + encodeURIComponent(tmpStreamURL));
1473
+
1474
+ this._showToast('Opening VLC...');
1475
+
1476
+ // Use a temporary anchor element to trigger the protocol handler
1477
+ // without navigating the current page away
1478
+ let tmpLink = document.createElement('a');
1479
+ tmpLink.href = tmpVLCURL;
1480
+ tmpLink.style.display = 'none';
1481
+ document.body.appendChild(tmpLink);
1482
+ tmpLink.click();
1483
+ document.body.removeChild(tmpLink);
1484
+ }
1485
+
1347
1486
  /**
1348
1487
  * Show a brief toast notification in the viewer.
1349
1488
  *
@@ -19,7 +19,7 @@ const libToolDetector = require('./RetoldRemote-ToolDetector.js');
19
19
  const libThumbnailCache = require('./RetoldRemote-ThumbnailCache.js');
20
20
 
21
21
  const _ImageExtensions = { 'png': true, 'jpg': true, 'jpeg': true, 'gif': true, 'webp': true, 'svg': true, 'bmp': true, 'ico': true, 'avif': true, 'tiff': true, 'tif': true, 'heic': true, 'heif': true };
22
- const _VideoExtensions = { 'mp4': true, 'webm': true, 'mov': true, 'mkv': true, 'avi': true, 'wmv': true, 'flv': true, 'm4v': true, 'ogv': true };
22
+ const _VideoExtensions = { 'mp4': true, 'webm': true, 'mov': true, 'mkv': true, 'avi': true, 'wmv': true, 'flv': true, 'm4v': true, 'ogv': true, 'mpg': true, 'mpeg': true, 'mpe': true, 'mpv': true, 'm2v': true, 'ts': true, 'mts': true, 'm2ts': true, 'vob': true, '3gp': true, '3g2': true, 'f4v': true, 'rm': true, 'rmvb': true, 'divx': true, 'asf': true, 'mxf': true, 'dv': true, 'nsv': true, 'nuv': true, 'y4m': true, 'wtv': true, 'swf': true, 'dat': true };
23
23
  const _AudioExtensions = { 'mp3': true, 'wav': true, 'ogg': true, 'flac': true, 'aac': true, 'm4a': true, 'wma': true, 'oga': true };
24
24
  const _DocumentExtensions = { 'pdf': true, 'epub': true, 'mobi': true, 'doc': true, 'docx': true };
25
25
 
@@ -340,6 +340,7 @@ class RetoldRemoteLayoutView extends libPictView
340
340
  tmpSettingsView.render();
341
341
  }
342
342
  }
343
+
343
344
  }
344
345
 
345
346
  _setupResizeHandle()
@@ -603,16 +603,13 @@ class RetoldRemoteMediaViewerView extends libPictView
603
603
  + '</button>';
604
604
  }
605
605
 
606
- // VLC option (v)
607
- if (tmpCapabilities.vlc)
608
- {
609
- tmpHTML += '<button class="retold-remote-video-action-btn" '
610
- + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._openWithVLC()" '
611
- + 'title="Open with VLC media player">'
612
- + '<span class="retold-remote-video-action-key">v</span>'
613
- + 'Open with VLC'
614
- + '</button>';
615
- }
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>';
616
613
 
617
614
  tmpHTML += '</div>';
618
615
 
@@ -655,14 +652,11 @@ class RetoldRemoteMediaViewerView extends libPictView
655
652
  + '</button>';
656
653
  }
657
654
 
658
- if (tmpCapabilities.vlc)
659
- {
660
- tmpHTML += '<button class="retold-remote-vlc-btn" '
661
- + 'onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._openWithVLC()" '
662
- + 'title="Open with VLC">'
663
- + 'Open with VLC'
664
- + '</button>';
665
- }
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>';
666
660
 
667
661
  tmpHTML += '</div>'; // end stats
668
662
  tmpHTML += '</div>'; // end wrap
@@ -737,19 +731,29 @@ class RetoldRemoteMediaViewerView extends libPictView
737
731
  + 'Your browser does not support the audio tag.'
738
732
  + '</audio>';
739
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
+
740
737
  // Explore Audio button (available when ffprobe is present)
741
738
  let tmpCapabilities = this.pict.AppData.RetoldRemote.ServerCapabilities || {};
742
739
  if (tmpCapabilities.ffprobe || tmpCapabilities.ffmpeg)
743
740
  {
744
- tmpHTML += '<div style="margin-top: 20px;">'
745
- + '<button class="retold-remote-explore-btn" '
741
+ tmpHTML += '<button class="retold-remote-explore-btn" '
746
742
  + 'onclick="pict.views[\'RetoldRemote-AudioExplorer\'].showExplorer(pict.AppData.RetoldRemote.CurrentViewerFile)" '
747
743
  + 'title="Explore waveform and extract segments from this audio">'
748
- + '&#128202; Explore Audio'
749
- + '</button>'
750
- + '</div>';
744
+ + 'Explore Audio'
745
+ + '</button>';
751
746
  }
752
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
+
753
757
  tmpHTML += '</div>';
754
758
  return tmpHTML;
755
759
  }
@@ -77,6 +77,26 @@ 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
+ }
80
100
  .retold-remote-settings-shortcut-group
81
101
  {
82
102
  margin-bottom: 10px;
@@ -275,6 +295,14 @@ class RetoldRemoteSettingsPanelView extends libPictView
275
295
  tmpHTML += '</div>';
276
296
  tmpHTML += '</div>'; // end capabilities section
277
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
+
278
306
  // Keyboard shortcuts
279
307
  tmpHTML += '<div class="retold-remote-settings-section">';
280
308
  tmpHTML += '<div class="retold-remote-settings-section-title">Keyboard Shortcuts</div>';
@@ -291,6 +319,7 @@ class RetoldRemoteSettingsPanelView extends libPictView
291
319
  [
292
320
  { key: '\u2190 \u2191 \u2192 \u2193', desc: 'Navigate items' },
293
321
  { key: 'Enter', desc: 'Open item' },
322
+ { key: '1 2 3 4', desc: 'Open as image / video / audio / text' },
294
323
  { key: 'Esc', desc: 'Go up one folder' },
295
324
  { key: 'Home', desc: 'Jump to first item' },
296
325
  { key: 'End', desc: 'Jump to last item' },
@@ -316,9 +345,11 @@ class RetoldRemoteSettingsPanelView extends libPictView
316
345
  { key: 'Esc', desc: 'Back to gallery' },
317
346
  { key: '\u2192 j', desc: 'Next file' },
318
347
  { key: '\u2190 k', desc: 'Previous file' },
348
+ { key: '1 2 3 4', desc: 'View as image / video / audio / text' },
319
349
  { key: 'Space', desc: 'Play / pause' },
320
350
  { key: 'f', desc: 'Fullscreen' },
321
351
  { key: 'i', desc: 'File info overlay' },
352
+ { key: 'v', desc: 'Stream with VLC' },
322
353
  { key: '+ -', desc: 'Zoom in / out' },
323
354
  { key: '0', desc: 'Reset zoom' },
324
355
  { key: 'z', desc: 'Cycle fit mode' },
@@ -331,7 +362,7 @@ class RetoldRemoteSettingsPanelView extends libPictView
331
362
  { key: 'Enter', desc: 'Play in browser' },
332
363
  { key: 'e', desc: 'Explore video frames' },
333
364
  { key: 't', desc: 'Extract thumbnail' },
334
- { key: 'v', desc: 'Open with VLC' },
365
+ { key: 'v', desc: 'Stream with VLC' },
335
366
  { key: '\u2192 j', desc: 'Next file' },
336
367
  { key: '\u2190 k', desc: 'Previous file' },
337
368
  { key: 'Esc', desc: 'Back to gallery' }