retold-remote 0.0.13 → 0.0.15

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.
Files changed (28) hide show
  1. package/css/retold-remote.css +75 -0
  2. package/docs/_sidebar.md +2 -0
  3. package/docs/ultravisor-configuration.md +212 -0
  4. package/docs/ultravisor-integration.md +140 -0
  5. package/package.json +121 -96
  6. package/source/cli/RetoldRemote-Server-Setup.js +26 -0
  7. package/source/providers/Pict-Provider-GalleryNavigation.js +11 -3
  8. package/source/providers/keyboard-handlers/KeyHandler-AudioExplorer.js +5 -0
  9. package/source/providers/keyboard-handlers/KeyHandler-VideoExplorer.js +16 -0
  10. package/source/server/RetoldRemote-AudioWaveformService.js +101 -23
  11. package/source/server/RetoldRemote-EbookService.js +119 -6
  12. package/source/server/RetoldRemote-ImageService.js +144 -0
  13. package/source/server/RetoldRemote-MediaService.js +208 -34
  14. package/source/server/RetoldRemote-ToolDetector.js +27 -3
  15. package/source/server/RetoldRemote-UltravisorDispatcher.js +288 -0
  16. package/source/server/RetoldRemote-VideoFrameService.js +309 -77
  17. package/source/views/PictView-Remote-AudioExplorer.js +28 -14
  18. package/source/views/PictView-Remote-ImageExplorer.js +31 -11
  19. package/source/views/PictView-Remote-VLCSetup.js +22 -12
  20. package/source/views/PictView-Remote-VideoExplorer.js +29 -14
  21. package/web-application/css/retold-remote.css +75 -0
  22. package/web-application/docs/_sidebar.md +2 -0
  23. package/web-application/docs/ultravisor-configuration.md +212 -0
  24. package/web-application/docs/ultravisor-integration.md +140 -0
  25. package/web-application/retold-remote.js +58 -22
  26. package/web-application/retold-remote.js.map +1 -1
  27. package/web-application/retold-remote.min.js +6 -6
  28. package/web-application/retold-remote.min.js.map +1 -1
@@ -3332,7 +3332,7 @@ pView._updateImagePreviews(pSegmentIndex);};/**
3332
3332
  * @param {number} pSegmentIndex - The internal segment index
3333
3333
  */pView._updateImagePreviews=function _updateImagePreviews(pSegmentIndex){let tmpPreviewEl=document.getElementById(`PictMDE-ImagePreview-${pSegmentIndex}`);if(!tmpPreviewEl){return;}let tmpEditor=pView._segmentEditors[pSegmentIndex];if(!tmpEditor){tmpPreviewEl.innerHTML='';tmpPreviewEl.classList.remove('pict-mde-has-images');return;}let tmpContent=tmpEditor.state.doc.toString();// Match markdown image syntax: ![alt text](url)
3334
3334
  let tmpImageRegex=/!\[([^\]]*)\]\(([^)]+)\)/g;let tmpMatches=[];let tmpMatch;while((tmpMatch=tmpImageRegex.exec(tmpContent))!==null){tmpMatches.push({alt:tmpMatch[1]||'image',url:tmpMatch[2]});}if(tmpMatches.length===0){tmpPreviewEl.innerHTML='';tmpPreviewEl.classList.remove('pict-mde-has-images');return;}// Build preview HTML
3335
- let tmpHTML='';for(let i=0;i<tmpMatches.length;i++){let tmpAlt=tmpMatches[i].alt.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');let tmpURL=tmpMatches[i].url.replace(/&/g,'&amp;').replace(/"/g,'&quot;');tmpHTML+=`<div class="pict-mde-image-preview-item"><img src="${tmpURL}" alt="${tmpAlt}" /><span class="pict-mde-image-preview-label">${tmpAlt}</span></div>`;}tmpPreviewEl.innerHTML=tmpHTML;tmpPreviewEl.classList.add('pict-mde-has-images');};/**
3335
+ let tmpHTML='';for(let i=0;i<tmpMatches.length;i++){let tmpAlt=tmpMatches[i].alt.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');let tmpResolvedURL=pView._resolveImageURL(tmpMatches[i].url);let tmpURL=tmpResolvedURL.replace(/&/g,'&amp;').replace(/"/g,'&quot;');tmpHTML+=`<div class="pict-mde-image-preview-item"><img src="${tmpURL}" alt="${tmpAlt}" /><span class="pict-mde-image-preview-label">${tmpAlt}</span></div>`;}tmpPreviewEl.innerHTML=tmpHTML;tmpPreviewEl.classList.add('pict-mde-has-images');};/**
3336
3336
  * Wire drag-and-drop events for image files on a segment editor container.
3337
3337
  *
3338
3338
  * These events are separate from the segment-reorder drag events.
@@ -3374,7 +3374,8 @@ if(pView._dragSourceIndex>=0){return;}if(!pEvent.dataTransfer||!pEvent.dataTrans
3374
3374
  */pView._updateRichPreviews=function _updateRichPreviews(pSegmentIndex){if(!pView.options.EnableRichPreview){return;}let tmpPreviewEl=document.getElementById(`PictMDE-RichPreview-${pSegmentIndex}`);if(!tmpPreviewEl){return;}let tmpEditor=pView._segmentEditors[pSegmentIndex];if(!tmpEditor){tmpPreviewEl.innerHTML='';tmpPreviewEl.classList.remove('pict-mde-has-rich-preview');return;}let tmpContent=tmpEditor.state.doc.toString();if(!tmpContent||tmpContent.trim().length===0){tmpPreviewEl.innerHTML='';tmpPreviewEl.classList.remove('pict-mde-has-rich-preview');return;}// Use pict-section-content's provider to parse the raw markdown into HTML
3375
3375
  let tmpProvider=pView._getContentProvider();let tmpRenderedHTML=tmpProvider.parseMarkdown(tmpContent);if(!tmpRenderedHTML||tmpRenderedHTML.trim().length===0){tmpPreviewEl.innerHTML='';tmpPreviewEl.classList.remove('pict-mde-has-rich-preview');return;}// Wrap the rendered HTML in a pict-content container so that
3376
3376
  // pict-section-content's CSS classes take effect
3377
- let tmpPreviewID=`PictMDE-RichPreviewBody-${pSegmentIndex}`;tmpPreviewEl.innerHTML=`<div class="pict-content" id="${tmpPreviewID}">${tmpRenderedHTML}</div>`;tmpPreviewEl.classList.add('pict-mde-has-rich-preview');// Bump generation counter for stale-render protection (mermaid is async)
3377
+ let tmpPreviewID=`PictMDE-RichPreviewBody-${pSegmentIndex}`;tmpPreviewEl.innerHTML=`<div class="pict-content" id="${tmpPreviewID}">${tmpRenderedHTML}</div>`;tmpPreviewEl.classList.add('pict-mde-has-rich-preview');// Resolve relative image URLs in the rendered HTML using ImageBaseURL
3378
+ if(pView.options.ImageBaseURL){let tmpImages=tmpPreviewEl.querySelectorAll('img');for(let i=0;i<tmpImages.length;i++){let tmpSrc=tmpImages[i].getAttribute('src');if(tmpSrc){let tmpResolved=pView._resolveImageURL(tmpSrc);if(tmpResolved!==tmpSrc){tmpImages[i].setAttribute('src',tmpResolved);}}}}// Bump generation counter for stale-render protection (mermaid is async)
3378
3379
  let tmpGeneration=(pView._richPreviewGenerations[pSegmentIndex]||0)+1;pView._richPreviewGenerations[pSegmentIndex]=tmpGeneration;// Post-render: call mermaid.run() for mermaid diagram elements
3379
3380
  pView._postRenderMermaid(tmpPreviewID,pSegmentIndex,tmpGeneration);// Post-render: call katex.render() for KaTeX math elements
3380
3381
  pView._postRenderKaTeX(tmpPreviewID);};/**
@@ -3414,7 +3415,8 @@ pView._marshalAllEditorsToData();// Combine all segments into a single markdown
3414
3415
  let tmpSegments=pView._getSegmentsFromData();let tmpCombinedContent='';if(tmpSegments&&tmpSegments.length>0){let tmpParts=[];for(let i=0;i<tmpSegments.length;i++){tmpParts.push(tmpSegments[i].Content||'');}tmpCombinedContent=tmpParts.join('\n\n');}// Destroy existing CodeMirror editors
3415
3416
  for(let tmpIdx in pView._segmentEditors){if(pView._segmentEditors[tmpIdx]){pView._segmentEditors[tmpIdx].destroy();}}pView._segmentEditors={};// Render the combined markdown via pict-section-content
3416
3417
  let tmpProvider=pView._getContentProvider();let tmpRenderedHTML=tmpProvider.parseMarkdown(tmpCombinedContent);// Build the rendered view container
3417
- let tmpRenderedViewID='PictMDE-RenderedView';tmpContainer.innerHTML=`<div class="pict-mde-rendered-view" id="${tmpRenderedViewID}"><div class="pict-content">${tmpRenderedHTML||''}</div></div>`;tmpContainer.classList.add('pict-mde-rendered-mode');// Bump generation for stale-render protection
3418
+ let tmpRenderedViewID='PictMDE-RenderedView';tmpContainer.innerHTML=`<div class="pict-mde-rendered-view" id="${tmpRenderedViewID}"><div class="pict-content">${tmpRenderedHTML||''}</div></div>`;tmpContainer.classList.add('pict-mde-rendered-mode');// Resolve relative image URLs in the rendered HTML using ImageBaseURL
3419
+ if(pView.options.ImageBaseURL){let tmpImages=tmpContainer.querySelectorAll('.pict-mde-rendered-view img');for(let i=0;i<tmpImages.length;i++){let tmpSrc=tmpImages[i].getAttribute('src');if(tmpSrc){let tmpResolved=pView._resolveImageURL(tmpSrc);if(tmpResolved!==tmpSrc){tmpImages[i].setAttribute('src',tmpResolved);}}}}// Bump generation for stale-render protection
3418
3420
  pView._renderedViewGeneration++;let tmpGeneration=pView._renderedViewGeneration;// Post-render hooks for mermaid diagrams and KaTeX equations
3419
3421
  let tmpContentContainer=tmpContainer.querySelector(`#${tmpRenderedViewID} .pict-content`);if(tmpContentContainer){let tmpContentID='PictMDE-RenderedViewContent';tmpContentContainer.id=tmpContentID;pView._postRenderMermaid(tmpContentID,-1,tmpGeneration);pView._postRenderKaTeX(tmpContentID);}};/**
3420
3422
  * Switch back from rendered view to the editing view: rebuild the
@@ -3445,7 +3447,11 @@ let tmpContentContainer=tmpContainer.querySelector(`#${tmpRenderedViewID} .pict-
3445
3447
  // code, mermaid diagrams, KaTeX equations, tables, etc. via pict-section-content).
3446
3448
  // Requires the consumer to load the mermaid and/or katex libraries via CDN
3447
3449
  // for diagram/equation rendering; code highlighting works without CDN scripts.
3448
- "EnableRichPreview":true,// ---- Quadrant button definitions ----
3450
+ "EnableRichPreview":true,// Base URL prepended to relative image URLs in image and rich previews.
3451
+ // Set this to the directory-level path (e.g. "/content/") so that images
3452
+ // referenced by filename in the markdown resolve correctly.
3453
+ // Absolute URLs (starting with /, http://, https://, data:) are left as-is.
3454
+ "ImageBaseURL":"",// ---- Quadrant button definitions ----
3449
3455
  // Each quadrant is an array of button objects:
3450
3456
  // HTML — innerHTML for the button
3451
3457
  // Action — method name, optionally "method:arg" (receives segment index as first param)
@@ -4160,6 +4166,16 @@ this._buildEditorUI();}/**
4160
4166
  *
4161
4167
  * @returns {HTMLElement|null}
4162
4168
  */_getContainerElement(){if(this.targetElement){let tmpContainer=this.targetElement.querySelector('.pict-mde');if(tmpContainer){return tmpContainer;}}return this.targetElement||null;}/**
4169
+ * Resolve a URL relative to the configured ImageBaseURL.
4170
+ *
4171
+ * Absolute URLs (starting with /, http://, https://, data:) are returned
4172
+ * unchanged. Relative URLs are prepended with this.options.ImageBaseURL.
4173
+ *
4174
+ * @param {string} pURL - The URL to resolve
4175
+ * @returns {string} The resolved URL
4176
+ */_resolveImageURL(pURL){if(!pURL||!this.options.ImageBaseURL){return pURL;}// Leave absolute URLs alone
4177
+ if(pURL.startsWith('/')||pURL.startsWith('http://')||pURL.startsWith('https://')||pURL.startsWith('data:')){return pURL;}let tmpBase=this.options.ImageBaseURL;// Ensure base ends with /
4178
+ if(tmpBase&&!tmpBase.endsWith('/')){tmpBase+='/';}return tmpBase+pURL;}/**
4163
4179
  * Build the full editor UI: render existing segments from data and the add-segment button.
4164
4180
  */_buildEditorUI(){let tmpContainer=this._getContainerElement();// Ensure the container has the pict-mde class (the template's wrapper
4165
4181
  // may have been replaced by pict's async render pipeline)
@@ -9872,7 +9888,8 @@ let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.FolderCursorHistory[tmpCu
9872
9888
  */closeViewer(){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='gallery';// Clear viewer file state so collection/favorites resolution
9873
9889
  // falls through to the gallery cursor instead of a stale path
9874
9890
  tmpRemote.CurrentViewerFile='';tmpRemote.CurrentViewerMediaType='';// Exit collection browsing mode
9875
- tmpRemote.BrowsingCollection=false;tmpRemote.BrowsingCollectionIndex=-1;let tmpGalleryContainer=document.getElementById('RetoldRemote-Gallery-Container');let tmpViewerContainer=document.getElementById('RetoldRemote-Viewer-Container');if(tmpGalleryContainer)tmpGalleryContainer.style.display='';if(tmpViewerContainer)tmpViewerContainer.style.display='none';// Clean up swipe and DF exit listeners
9891
+ tmpRemote.BrowsingCollection=false;tmpRemote.BrowsingCollectionIndex=-1;let tmpGalleryContainer=document.getElementById('RetoldRemote-Gallery-Container');let tmpViewerContainer=document.getElementById('RetoldRemote-Viewer-Container');// Stop any playing video/audio and release resources before hiding the viewer
9892
+ let tmpVideo=document.querySelector('#RetoldRemote-Viewer-Container video');let tmpAudio=document.querySelector('#RetoldRemote-Viewer-Container audio');if(tmpVideo){tmpVideo.pause();tmpVideo.removeAttribute('src');tmpVideo.load();}if(tmpAudio){tmpAudio.pause();tmpAudio.removeAttribute('src');tmpAudio.load();}if(tmpGalleryContainer)tmpGalleryContainer.style.display='';if(tmpViewerContainer)tmpViewerContainer.style.display='none';// Clean up swipe and DF exit listeners
9876
9893
  let tmpMediaViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpMediaViewer){if(tmpMediaViewer._cleanupSwipe){tmpMediaViewer._cleanupSwipe();}if(tmpMediaViewer._cleanupDFExitHotspot){tmpMediaViewer._cleanupDFExitHotspot();}}// Restore the hash to the browse route (use hashed identifier when available)
9877
9894
  let tmpCurrentLocation=this.pict.AppData.PictFileBrowser&&this.pict.AppData.PictFileBrowser.CurrentLocation||'';let tmpFragProvider=this.pict.providers['RetoldRemote-Provider'];let tmpFragId=tmpFragProvider&&tmpCurrentLocation?tmpFragProvider.getFragmentIdentifier(tmpCurrentLocation):tmpCurrentLocation;window.location.hash=tmpFragId?'#/browse/'+tmpFragId:'#/browse/';// Re-render gallery to ensure cursor is visible
9878
9895
  let tmpGalleryView=this.pict.views['RetoldRemote-Gallery'];if(tmpGalleryView){tmpGalleryView.renderGallery();}}/**
@@ -9968,8 +9985,10 @@ fetch('/api/media/open',{method:'POST',headers:{'Content-Type':'application/json
9968
9985
  */_streamWithVLC(){let tmpRemote=this.pict.AppData.RetoldRemote;let tmpMediaType=tmpRemote.CurrentViewerMediaType;if(tmpMediaType!=='video'&&tmpMediaType!=='audio'){return;}let tmpFilePath=tmpRemote.CurrentViewerFile;if(!tmpFilePath){return;}let tmpProvider=this.pict.providers['RetoldRemote-Provider'];let tmpContentPath=tmpProvider?tmpProvider.getContentURL(tmpFilePath):'/content/'+encodeURIComponent(tmpFilePath);let tmpStreamURL=window.location.origin+tmpContentPath;// Detect platform for VLC URL format.
9969
9986
  // iPadOS 13+ sends a macOS user agent — detect it via maxTouchPoints.
9970
9987
  let tmpIsWindows=/Windows/.test(navigator.userAgent);let tmpIsIOS=/iPhone|iPad|iPod/i.test(navigator.userAgent)||/Macintosh/i.test(navigator.userAgent)&&navigator.maxTouchPoints>1;let tmpIsAndroid=/Android/i.test(navigator.userAgent);let tmpIsMobile=tmpIsIOS||tmpIsAndroid;let tmpVLCURL;if(tmpIsIOS){// iOS/iPadOS: use vlc-x-callback for reliable app launching
9971
- tmpVLCURL='vlc-x-callback://x-callback-url/stream?url='+encodeURIComponent(tmpStreamURL);}else if(tmpIsWindows||tmpIsAndroid){// Windows and Android: VLC handles the raw URL natively
9972
- tmpVLCURL='vlc://'+tmpStreamURL;}else{// macOS/Linux: our custom handlers URL-decode, so we encode
9988
+ tmpVLCURL='vlc-x-callback://x-callback-url/stream?url='+encodeURIComponent(tmpStreamURL);}else if(tmpIsAndroid){// Android: VLC app handles the raw URL natively via intent
9989
+ tmpVLCURL='vlc://'+tmpStreamURL;}else{// Windows, macOS, Linux: encode the URL so the custom protocol
9990
+ // handler can decode it. On Windows this is required because the
9991
+ // shell strips the colon from nested http:// URLs.
9973
9992
  tmpVLCURL='vlc://'+encodeURIComponent(tmpStreamURL);}this.pict.providers['RetoldRemote-ToastNotification'].showOverlayIndicator('Opening VLC...');// Use window.location for iOS (more reliable for custom URL schemes
9974
9993
  // on iOS Safari than a programmatic anchor click), anchor for others.
9975
9994
  if(tmpIsIOS){window.location.href=tmpVLCURL;}else{let tmpLink=document.createElement('a');tmpLink.href=tmpVLCURL;tmpLink.style.display='none';document.body.appendChild(tmpLink);tmpLink.click();document.body.removeChild(tmpLink);}}}GalleryNavigationProvider.default_configuration=_DefaultProviderConfiguration;module.exports=GalleryNavigationProvider;},{"./keyboard-handlers/KeyHandler-AudioExplorer.js":128,"./keyboard-handlers/KeyHandler-Gallery.js":129,"./keyboard-handlers/KeyHandler-ImageExplorer.js":130,"./keyboard-handlers/KeyHandler-Sidebar.js":131,"./keyboard-handlers/KeyHandler-VideoExplorer.js":132,"./keyboard-handlers/KeyHandler-Viewer.js":133,"pict-provider":46}],123:[function(require,module,exports){const libPictProvider=require('pict-provider');const libExtensionMaps=require('../RetoldRemote-ExtensionMaps.js');const _DefaultProviderConfiguration={ProviderIdentifier:'RetoldRemote-Provider',AutoInitialize:true,AutoSolveWithApp:false};class RetoldRemoteProvider extends libPictProvider{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);// Client-side cache: path -> hash and hash -> path
@@ -10277,7 +10296,7 @@ let tmpRemote=this.pict.AppData.RetoldRemote;if(tmpRemote){tmpRemote.Theme=pThem
10277
10296
  *
10278
10297
  * @param {GalleryNavigationProvider} pGalleryNav - The provider instance
10279
10298
  * @param {KeyboardEvent} pEvent - The keyboard event
10280
- */function handleAudioExplorerKey(pGalleryNav,pEvent){let tmpAEX=pGalleryNav.pict.views['RetoldRemote-AudioExplorer'];if(!tmpAEX){return;}switch(pEvent.key){case'Escape':pEvent.preventDefault();if(tmpAEX._selectionStart>=0){tmpAEX.clearSelection();}else{tmpAEX.goBack();}break;case'+':case'=':pEvent.preventDefault();tmpAEX.zoomIn();break;case'-':case'_':pEvent.preventDefault();tmpAEX.zoomOut();break;case'0':pEvent.preventDefault();tmpAEX.zoomToFit();break;case'z':case'Z':pEvent.preventDefault();tmpAEX.zoomToSelection();break;case' ':pEvent.preventDefault();tmpAEX.playSelection();break;case'a':case's':pEvent.preventDefault();{let tmpCollMgr=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpCollMgr){let tmpQuickGUID=tmpCollMgr.getQuickAddTargetGUID();if(tmpQuickGUID){tmpCollMgr.addAudioSnippetToCollection(tmpQuickGUID);}else{let tmpTopBar=pGalleryNav.pict.views['ContentEditor-TopBar'];if(tmpTopBar&&typeof tmpTopBar.showAddToCollectionDropdown==='function'){tmpTopBar.showAddToCollectionDropdown();}}}}break;case'b':pEvent.preventDefault();{let tmpCollManager=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpCollManager){tmpCollManager.togglePanel();}}break;case'h':pEvent.preventDefault();{let tmpFavCollManager=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpFavCollManager){tmpFavCollManager.toggleFavorite();}}break;}}module.exports=handleAudioExplorerKey;},{}],129:[function(require,module,exports){/**
10299
+ */function handleAudioExplorerKey(pGalleryNav,pEvent){let tmpAEX=pGalleryNav.pict.views['RetoldRemote-AudioExplorer'];if(!tmpAEX){return;}switch(pEvent.key){case'Escape':pEvent.preventDefault();if(tmpAEX._selectionStart>=0){tmpAEX.clearSelection();}else{tmpAEX.goBack();}break;case'+':case'=':pEvent.preventDefault();tmpAEX.zoomIn();break;case'-':case'_':pEvent.preventDefault();tmpAEX.zoomOut();break;case'0':pEvent.preventDefault();tmpAEX.zoomToFit();break;case'z':case'Z':pEvent.preventDefault();tmpAEX.zoomToSelection();break;case' ':pEvent.preventDefault();tmpAEX.playSelection();break;case'a':case's':pEvent.preventDefault();{let tmpCollMgr=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpCollMgr){let tmpQuickGUID=tmpCollMgr.getQuickAddTargetGUID();if(tmpQuickGUID){tmpCollMgr.addAudioSnippetToCollection(tmpQuickGUID);}else{let tmpTopBar=pGalleryNav.pict.views['ContentEditor-TopBar'];if(tmpTopBar&&typeof tmpTopBar.showAddToCollectionDropdown==='function'){tmpTopBar.showAddToCollectionDropdown();}}}}break;case'b':pEvent.preventDefault();{let tmpCollManager=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpCollManager){tmpCollManager.togglePanel();}}break;case'h':pEvent.preventDefault();{let tmpFavCollManager=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpFavCollManager){tmpFavCollManager.toggleFavorite();}}break;case'v':pEvent.preventDefault();pGalleryNav._streamWithVLC();break;}}module.exports=handleAudioExplorerKey;},{}],129:[function(require,module,exports){/**
10281
10300
  * Gallery mode keyboard handler.
10282
10301
  *
10283
10302
  * @param {GalleryNavigationProvider} pGalleryNav - The provider instance
@@ -10310,7 +10329,7 @@ if(tmpVEX._previewKeyHandler){tmpVEX.closeFramePreview();}else{tmpVEX.goBack();}
10310
10329
  let tmpSelVEX=pGalleryNav.pict.views['RetoldRemote-VideoExplorer'];if(!tmpSelVEX||tmpSelVEX._selectionStartTime<0||tmpSelVEX._selectionEndTime<0){// No selection active \u2014 do nothing
10311
10330
  break;}let tmpSelCollMgr=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpSelCollMgr){let tmpStart=Math.min(tmpSelVEX._selectionStartTime,tmpSelVEX._selectionEndTime);let tmpEnd=Math.max(tmpSelVEX._selectionStartTime,tmpSelVEX._selectionEndTime);let tmpSelQuickGUID=tmpSelCollMgr.getQuickAddTargetGUID();if(tmpSelQuickGUID){tmpSelCollMgr.addVideoClipToCollection(tmpSelQuickGUID,tmpStart,tmpEnd);}else{let tmpSelTopBar=pGalleryNav.pict.views['ContentEditor-TopBar'];if(tmpSelTopBar&&typeof tmpSelTopBar.showAddToCollectionDropdown==='function'){tmpSelTopBar.showAddToCollectionDropdown();}}}}break;case'[':pEvent.preventDefault();{// Set selection start marker at currently selected frame's timestamp
10312
10331
  let tmpStartVEX=pGalleryNav.pict.views['RetoldRemote-VideoExplorer'];if(tmpStartVEX&&tmpStartVEX._frameData&&tmpStartVEX._frameData.Frames&&tmpStartVEX._selectedFrameIndex>=0&&tmpStartVEX._frameData.Frames[tmpStartVEX._selectedFrameIndex]){let tmpTimestamp=tmpStartVEX._frameData.Frames[tmpStartVEX._selectedFrameIndex].Timestamp;tmpStartVEX.setSelectionStart(tmpTimestamp);}}break;case']':pEvent.preventDefault();{// Set selection end marker at currently selected frame's timestamp
10313
- let tmpEndVEX=pGalleryNav.pict.views['RetoldRemote-VideoExplorer'];if(tmpEndVEX&&tmpEndVEX._frameData&&tmpEndVEX._frameData.Frames&&tmpEndVEX._selectedFrameIndex>=0&&tmpEndVEX._frameData.Frames[tmpEndVEX._selectedFrameIndex]){let tmpTimestamp=tmpEndVEX._frameData.Frames[tmpEndVEX._selectedFrameIndex].Timestamp;tmpEndVEX.setSelectionEnd(tmpTimestamp);}}break;case'b':pEvent.preventDefault();{let tmpCollManager=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpCollManager){tmpCollManager.togglePanel();}}break;case'h':pEvent.preventDefault();{let tmpFavCollManager=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpFavCollManager){tmpFavCollManager.toggleFavorite();}}break;}}module.exports=handleVideoExplorerKey;},{}],133:[function(require,module,exports){/**
10332
+ let tmpEndVEX=pGalleryNav.pict.views['RetoldRemote-VideoExplorer'];if(tmpEndVEX&&tmpEndVEX._frameData&&tmpEndVEX._frameData.Frames&&tmpEndVEX._selectedFrameIndex>=0&&tmpEndVEX._frameData.Frames[tmpEndVEX._selectedFrameIndex]){let tmpTimestamp=tmpEndVEX._frameData.Frames[tmpEndVEX._selectedFrameIndex].Timestamp;tmpEndVEX.setSelectionEnd(tmpTimestamp);}}break;case'b':pEvent.preventDefault();{let tmpCollManager=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpCollManager){tmpCollManager.togglePanel();}}break;case'h':pEvent.preventDefault();{let tmpFavCollManager=pGalleryNav.pict.providers['RetoldRemote-CollectionManager'];if(tmpFavCollManager){tmpFavCollManager.toggleFavorite();}}break;case'v':pEvent.preventDefault();pGalleryNav._streamWithVLC();break;case' ':pEvent.preventDefault();{let tmpPlayVEX=pGalleryNav.pict.views['RetoldRemote-VideoExplorer'];if(tmpPlayVEX){tmpPlayVEX.playInBrowser();}}break;}}module.exports=handleVideoExplorerKey;},{}],133:[function(require,module,exports){/**
10314
10333
  * Viewer mode keyboard handler.
10315
10334
  *
10316
10335
  * @param {GalleryNavigationProvider} pGalleryNav - The provider instance
@@ -10414,13 +10433,13 @@ this._mainCanvas=null;this._overviewCanvas=null;this._resizeObserver=null;}/**
10414
10433
  * @param {string} pFilePath - Relative file path
10415
10434
  * @param {number} [pSelectionStart] - Optional selection start in seconds
10416
10435
  * @param {number} [pSelectionEnd] - Optional selection end in seconds
10417
- */showExplorer(pFilePath,pSelectionStart,pSelectionEnd){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='audio-explorer';this._currentPath=pFilePath;this._waveformData=null;this._peaks=[];this._viewStart=0;this._viewEnd=1;this._segmentURL=null;// Store passed-in selection times (in seconds) to apply after waveform loads
10436
+ */showExplorer(pFilePath,pSelectionStart,pSelectionEnd){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='audio-explorer';tmpRemote.CurrentViewerFile=pFilePath;tmpRemote.CurrentViewerMediaType='audio';this._currentPath=pFilePath;this._waveformData=null;this._peaks=[];this._viewStart=0;this._viewEnd=1;this._segmentURL=null;// Store passed-in selection times (in seconds) to apply after waveform loads
10418
10437
  this._pendingSelectionStartSec=typeof pSelectionStart==='number'&&pSelectionStart>=0?pSelectionStart:-1;this._pendingSelectionEndSec=typeof pSelectionEnd==='number'&&pSelectionEnd>=0?pSelectionEnd:-1;this._selectionStart=-1;this._selectionEnd=-1;// Update the hash. Replace (not push) when coming from #/view/ to
10419
10438
  // prevent back-button loops when auto-launched from the media viewer.
10420
10439
  let tmpFragProvider=this.pict.providers['RetoldRemote-Provider'];let tmpFragId=tmpFragProvider?tmpFragProvider.getFragmentIdentifier(pFilePath):pFilePath;let tmpNewHash='#/explore-audio/'+tmpFragId;let tmpCurrentHash=window.location.hash||'';if(tmpCurrentHash.indexOf('#/view/')===0){history.replaceState(null,'',tmpNewHash);}else{window.location.hash=tmpNewHash;}// Show viewer container, hide gallery
10421
10440
  let tmpGalleryContainer=document.getElementById('RetoldRemote-Gallery-Container');let tmpViewerContainer=document.getElementById('RetoldRemote-Viewer-Container');if(tmpGalleryContainer)tmpGalleryContainer.style.display='none';if(tmpViewerContainer)tmpViewerContainer.style.display='block';let tmpFileName=pFilePath.replace(/^.*\//,'');// Build initial UI
10422
10441
  let tmpHTML='<div class="retold-remote-aex">';// Header
10423
- tmpHTML+='<div class="retold-remote-aex-header">';tmpHTML+='<button class="retold-remote-aex-nav-btn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].goBack()" title="Back to audio (Esc)">&larr; Back</button>';tmpHTML+='<div class="retold-remote-aex-title">Audio Explorer &mdash; '+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpFileName)+'</div>';tmpHTML+='</div>';// Info bar (populated after waveform loads)
10442
+ tmpHTML+='<div class="retold-remote-aex-header">';tmpHTML+='<button class="retold-remote-aex-nav-btn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].goBack()" title="Back (Esc)">&larr; Back</button>';tmpHTML+='<div class="retold-remote-aex-title">Audio Explorer &mdash; '+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpFileName)+'</div>';tmpHTML+='<div class="retold-remote-aex-actions">';tmpHTML+='<button class="retold-remote-aex-action-btn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].playInBrowser()" title="Play in browser">&#9654; Play</button>';tmpHTML+='<button class="retold-remote-aex-action-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" title="Stream with VLC (v)">&#9654; VLC</button>';tmpHTML+='</div>';tmpHTML+='</div>';// Info bar (populated after waveform loads)
10424
10443
  tmpHTML+='<div class="retold-remote-aex-info" id="RetoldRemote-AEX-Info" style="display:none;"></div>';// Controls bar
10425
10444
  tmpHTML+='<div class="retold-remote-aex-controls" id="RetoldRemote-AEX-Controls" style="display:none;">';tmpHTML+='<button class="retold-remote-aex-btn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].zoomIn()" title="Zoom In (+)">+ Zoom In</button>';tmpHTML+='<button class="retold-remote-aex-btn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].zoomOut()" title="Zoom Out (-)">- Zoom Out</button>';tmpHTML+='<button class="retold-remote-aex-btn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].zoomToFit()" title="Zoom to Fit (0)">Fit All</button>';tmpHTML+='<button class="retold-remote-aex-btn" id="RetoldRemote-AEX-ZoomSelBtn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].zoomToSelection()" title="Zoom to Selection (Z)" disabled>Zoom to Selection</button>';tmpHTML+='<button class="retold-remote-aex-btn" id="RetoldRemote-AEX-PlaySelBtn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].playSelection()" title="Play Selection (Space)" disabled>&#9654; Play Selection</button>';tmpHTML+='<button class="retold-remote-aex-btn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].clearSelection()" title="Clear Selection (Esc)">Clear Selection</button>';tmpHTML+='<span style="border-left:1px solid var(--retold-border);height:20px;margin:0 4px;"></span>';tmpHTML+='<button class="retold-remote-aex-save-btn" id="RetoldRemote-AEX-SaveSelBtn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].saveSelectionToCollection()" title="Save segment to collection (s)" disabled>Save Segment</button>';tmpHTML+='</div>';// Body (loading initially)
10426
10445
  tmpHTML+='<div class="retold-remote-aex-body" id="RetoldRemote-AEX-Body">';tmpHTML+='<div class="retold-remote-aex-loading">';tmpHTML+='<div class="retold-remote-aex-loading-spinner"></div>';tmpHTML+='Analyzing audio waveform...';tmpHTML+='</div>';tmpHTML+='</div>';// Time display bar
@@ -10506,9 +10525,12 @@ let tmpDuration=this._waveformData&&this._waveformData.Duration?this._waveformDa
10506
10525
  */playSelection(){if(this._selectionStart<0||this._selectionEnd<0||!this._waveformData){return;}let tmpSelf=this;let tmpDuration=this._waveformData.Duration;let tmpStart=Math.min(this._selectionStart,this._selectionEnd)*tmpDuration;let tmpEnd=Math.max(this._selectionStart,this._selectionEnd)*tmpDuration;let tmpProvider=this.pict.providers['RetoldRemote-Provider'];let tmpPathParam=tmpProvider?tmpProvider._getPathParam(this._currentPath):encodeURIComponent(this._currentPath);let tmpURL='/api/media/audio-segment?path='+tmpPathParam+'&start='+tmpStart.toFixed(3)+'&end='+tmpEnd.toFixed(3)+'&format=mp3';// Show playback bar with loading state
10507
10526
  let tmpPlaybackBar=document.getElementById('RetoldRemote-AEX-Playback');if(tmpPlaybackBar){tmpPlaybackBar.style.display='';}let tmpAudioEl=document.getElementById('RetoldRemote-AEX-Audio');if(tmpAudioEl){tmpAudioEl.src=tmpURL;tmpAudioEl.load();tmpAudioEl.play().catch(()=>{// Autoplay may be blocked; user can click play
10508
10527
  });}this._segmentURL=tmpURL;}/**
10509
- * Navigate back to the audio player viewer.
10528
+ * Navigate back to the gallery / file listing.
10510
10529
  */goBack(){// Clean up resize observer
10511
- if(this._resizeObserver){this._resizeObserver.disconnect();this._resizeObserver=null;}if(this._currentPath){let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(this._currentPath,'audio');}}else{let tmpNav=this.pict.providers['RetoldRemote-GalleryNavigation'];if(tmpNav){tmpNav.closeViewer();}}}// --- Explorer State Persistence ---
10530
+ if(this._resizeObserver){this._resizeObserver.disconnect();this._resizeObserver=null;}let tmpNav=this.pict.providers['RetoldRemote-GalleryNavigation'];if(tmpNav){tmpNav.closeViewer();}}/**
10531
+ * Leave the audio explorer and play the file in the browser viewer.
10532
+ */playInBrowser(){// Clean up resize observer
10533
+ if(this._resizeObserver){this._resizeObserver.disconnect();this._resizeObserver=null;}let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(this._currentPath,'audio');}}// --- Explorer State Persistence ---
10512
10534
  /**
10513
10535
  * Fire-and-forget save of the current selection and view state.
10514
10536
  */_saveState(){if(!this._currentPath||!this._waveformData){return;}let tmpProvider=this.pict.providers['RetoldRemote-Provider'];let tmpSelections=[];if(this._selectionStart>=0&&this._selectionEnd>=0&&Math.abs(this._selectionEnd-this._selectionStart)>=0.001){let tmpDuration=this._waveformData.Duration||0;let tmpStartNorm=Math.min(this._selectionStart,this._selectionEnd);let tmpEndNorm=Math.max(this._selectionStart,this._selectionEnd);let tmpStartSec=Math.round(tmpStartNorm*tmpDuration*100)/100;let tmpEndSec=Math.round(tmpEndNorm*tmpDuration*100)/100;let tmpFmt=this.pict.providers['RetoldRemote-FormattingUtilities'];tmpSelections.push({Start:tmpStartNorm,End:tmpEndNorm,StartSeconds:tmpStartSec,EndSeconds:tmpEndSec,Label:tmpFmt.formatTimestamp(tmpStartSec,true)+' - '+tmpFmt.formatTimestamp(tmpEndSec,true)});}let tmpBody={Path:this._currentPath,Selections:tmpSelections,ViewStart:this._viewStart,ViewEnd:this._viewEnd};fetch('/api/media/audio-explorer-state',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(tmpBody)}).catch(()=>{// Non-critical — explorer works without persistence
@@ -10782,7 +10804,7 @@ let tmpExt=(pExtension||'').replace(/^\./,'').toLowerCase();if(tmpExt==='png'||t
10782
10804
  * Show the image explorer for a given image file.
10783
10805
  *
10784
10806
  * @param {string} pFilePath - Relative file path
10785
- */showExplorer(pFilePath){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='image-explorer';this._currentPath=pFilePath;this._dziData=null;this._loading=false;// Clean up existing viewer
10807
+ */showExplorer(pFilePath){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='image-explorer';tmpRemote.CurrentViewerFile=pFilePath;tmpRemote.CurrentViewerMediaType='image';this._currentPath=pFilePath;this._dziData=null;this._loading=false;// Clean up existing viewer
10786
10808
  if(this._osdViewer){try{this._osdViewer.destroy();}catch(pErr){// ignore
10787
10809
  }this._osdViewer=null;}// Update URL hash.
10788
10810
  // When the current hash is #/view/ for the same file (i.e. the media
@@ -10791,7 +10813,7 @@ if(this._osdViewer){try{this._osdViewer.destroy();}catch(pErr){// ignore
10791
10813
  let tmpFragProvider=this.pict.providers['RetoldRemote-Provider'];let tmpFragId=tmpFragProvider?tmpFragProvider.getFragmentIdentifier(pFilePath):pFilePath;let tmpNewHash='#/explore-image/'+tmpFragId;let tmpCurrentHash=window.location.hash||'';if(tmpCurrentHash.indexOf('#/view/')===0){history.replaceState(null,'',tmpNewHash);}else{window.location.hash=tmpNewHash;}// Show viewer, hide gallery
10792
10814
  let tmpGalleryContainer=document.getElementById('RetoldRemote-Gallery-Container');let tmpViewerContainer=document.getElementById('RetoldRemote-Viewer-Container');if(tmpGalleryContainer)tmpGalleryContainer.style.display='none';if(tmpViewerContainer)tmpViewerContainer.style.display='block';let tmpFileName=pFilePath.replace(/^.*\//,'');let tmpFmt=this.pict.providers['RetoldRemote-FormattingUtilities'];// Build the explorer UI
10793
10815
  let tmpHTML='<div class="retold-remote-iex">';// Header
10794
- tmpHTML+='<div class="retold-remote-iex-header">';tmpHTML+='<button class="retold-remote-iex-nav-btn" onclick="pict.views[\'RetoldRemote-ImageExplorer\'].goBack()" title="Back (Esc)">&larr; Back</button>';tmpHTML+='<div class="retold-remote-iex-title">Image Explorer &mdash; '+tmpFmt.escapeHTML(tmpFileName)+'</div>';tmpHTML+='</div>';// Info bar
10816
+ tmpHTML+='<div class="retold-remote-iex-header">';tmpHTML+='<button class="retold-remote-iex-nav-btn" onclick="pict.views[\'RetoldRemote-ImageExplorer\'].goBack()" title="Back (Esc)">&larr; Back</button>';tmpHTML+='<div class="retold-remote-iex-title">Image Explorer &mdash; '+tmpFmt.escapeHTML(tmpFileName)+'</div>';tmpHTML+='<div class="retold-remote-iex-actions">';tmpHTML+='<button class="retold-remote-iex-action-btn" onclick="pict.views[\'RetoldRemote-ImageExplorer\'].viewInBrowser()" title="View in standard viewer">&#128444; View</button>';tmpHTML+='</div>';tmpHTML+='</div>';// Info bar
10795
10817
  tmpHTML+='<div class="retold-remote-iex-info" id="RetoldRemote-IEX-Info" style="display:none;"></div>';// Body
10796
10818
  tmpHTML+='<div class="retold-remote-iex-body" id="RetoldRemote-IEX-Body">';tmpHTML+='<div class="retold-remote-iex-loading" id="RetoldRemote-IEX-Loading">';tmpHTML+='<div>Loading image\u2026</div>';tmpHTML+='</div>';tmpHTML+='<div id="RetoldRemote-IEX-Viewer" style="display:none;"></div>';tmpHTML+='</div>';// Controls bar
10797
10819
  tmpHTML+='<div class="retold-remote-iex-controls" id="RetoldRemote-IEX-Controls" style="display:none;">';tmpHTML+='<button onclick="pict.views[\'RetoldRemote-ImageExplorer\'].zoomIn()" title="Zoom In (+)">+ Zoom In</button>';tmpHTML+='<span class="retold-remote-iex-zoom-label" id="RetoldRemote-IEX-ZoomLabel">100%</span>';tmpHTML+='<button onclick="pict.views[\'RetoldRemote-ImageExplorer\'].zoomOut()" title="Zoom Out (-)">- Zoom Out</button>';tmpHTML+='<button onclick="pict.views[\'RetoldRemote-ImageExplorer\'].zoomHome()" title="Fit to view (0)">Fit</button>';tmpHTML+='<span style="flex:1;"></span>';tmpHTML+='<span id="RetoldRemote-IEX-Coords" style="color:var(--retold-text-dim);font-size:0.72rem;"></span>';tmpHTML+='</div>';tmpHTML+='</div>';if(tmpViewerContainer){tmpViewerContainer.innerHTML=tmpHTML;}// Update topbar
@@ -10891,10 +10913,14 @@ let tmpHomeZoom=this._osdViewer.viewport.getHomeZoom();let tmpPercent=Math.round
10891
10913
  */zoomOut(){if(this._osdViewer){let tmpCurrentZoom=this._osdViewer.viewport.getZoom();this._osdViewer.viewport.zoomTo(tmpCurrentZoom/1.5);}}/**
10892
10914
  * Reset to home (fit to view).
10893
10915
  */zoomHome(){if(this._osdViewer){this._osdViewer.viewport.goHome();}}/**
10894
- * Navigate back to the image viewer.
10916
+ * Navigate back to the gallery / file listing.
10895
10917
  */goBack(){// Destroy the OSD viewer
10896
10918
  if(this._osdViewer){try{this._osdViewer.destroy();}catch(pErr){// ignore
10897
- }this._osdViewer=null;}if(this._currentPath){let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(this._currentPath,'image');}}else{let tmpNav=this.pict.providers['RetoldRemote-GalleryNavigation'];if(tmpNav){tmpNav.closeViewer();}}}/**
10919
+ }this._osdViewer=null;}let tmpNav=this.pict.providers['RetoldRemote-GalleryNavigation'];if(tmpNav){tmpNav.closeViewer();}}/**
10920
+ * Leave the image explorer and view the image in the standard viewer.
10921
+ */viewInBrowser(){// Destroy the OSD viewer
10922
+ if(this._osdViewer){try{this._osdViewer.destroy();}catch(pErr){// ignore
10923
+ }this._osdViewer=null;}let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(this._currentPath,'image');}}/**
10898
10924
  * Show an error message.
10899
10925
  *
10900
10926
  * @param {string} pMessage - Error message
@@ -11323,7 +11349,15 @@ tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="r
11323
11349
  tmpHTML+='<div class="retold-remote-vlc-setup-status">';tmpHTML+='<div class="retold-remote-vlc-setup-status-dot '+(this._detectPlatform()===tmpPlatform?'detected':'unknown')+'"></div>';tmpHTML+='<span>Detected platform: <b>'+this._getPlatformLabel(this._detectPlatform())+'</b></span>';tmpHTML+='</div>';// Platform tabs
11324
11350
  tmpHTML+='<div class="retold-remote-vlc-setup-platform-tabs">';tmpHTML+=this._buildPlatformTab('ios','iOS',tmpPlatform);tmpHTML+=this._buildPlatformTab('android','Android',tmpPlatform);tmpHTML+=this._buildPlatformTab('macos','macOS',tmpPlatform);tmpHTML+=this._buildPlatformTab('windows','Windows',tmpPlatform);tmpHTML+=this._buildPlatformTab('linux','Linux',tmpPlatform);tmpHTML+='</div>';// Platform-specific content
11325
11351
  tmpHTML+=this._buildIOSContent(tmpPlatform);tmpHTML+=this._buildAndroidContent(tmpPlatform);tmpHTML+=this._buildMacOSContent(tmpPlatform);tmpHTML+=this._buildWindowsContent(tmpPlatform);tmpHTML+=this._buildLinuxContent(tmpPlatform);// Test section
11326
- tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Test</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Click below to test whether the vlc:// protocol handler is registered. VLC should open.';tmpHTML+='</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn" onclick="pict.views[\'RetoldRemote-VLCSetup\'].testProtocol()">Test VLC Protocol</button>';tmpHTML+='</div>';tmpContainer.innerHTML=tmpHTML;}_buildPlatformTab(pKey,pLabel,pActive){let tmpClass='retold-remote-vlc-setup-platform-tab';if(pKey===pActive){tmpClass+=' active';}return'<button class="'+tmpClass+'" onclick="pict.views[\'RetoldRemote-VLCSetup\'].switchPlatformTab(\''+pKey+'\')">'+pLabel+'</button>';}_getPlatformLabel(pKey){if(pKey==='ios')return'iOS';if(pKey==='android')return'Android';if(pKey==='macos')return'macOS';if(pKey==='windows')return'Windows';return'Linux';}_buildIOSContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='ios'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="ios">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (iOS)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC for iOS registers the vlc:// protocol handler automatically when installed. No additional setup is needed.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Installation</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Install <b>VLC for Mobile</b> from the App Store if you haven\'t already.</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Tap the <b>Stream with VLC</b> button on any video or audio file. Safari will ask to open VLC — tap <b>Open</b>.</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">If Safari shows "Cannot Open Page", VLC may not be installed or may need to be updated.</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildAndroidContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='android'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="android">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (Android)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC for Android registers the vlc:// protocol handler automatically when installed. No additional setup is needed.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Installation</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Install <b>VLC for Android</b> from the Google Play Store if you haven\'t already.</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Tap the <b>Stream with VLC</b> button on any video or audio file. Your browser will ask to open VLC — tap <b>Open</b>.</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">If your browser shows an error, VLC may not be installed or may need to be updated.</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildMacOSContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='macos'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="macos">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (macOS)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC on macOS does not register a vlc:// protocol handler by default. ';tmpHTML+='An AppleScript app bundle is needed to bridge vlc:// links to VLC. ';tmpHTML+='Run the command below in Terminal to create and register the handler automatically.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Automatic Setup</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Copy and paste this into Terminal:';tmpHTML+='</div>';let tmpScript=this._getMacSetupScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyMacSetup()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">What This Does</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates an AppleScript at <code>/tmp/VLCProtocol.applescript</code> that handles vlc:// URLs</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Compiles it into an app bundle at <code>/Applications/VLCProtocol.app</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">3</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Adds the vlc:// URL scheme to the app\'s Info.plist</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">4</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Registers the protocol handler with macOS Launch Services</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">Requires VLC installed at /Applications/VLC.app and Python 3 (included with macOS).</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildWindowsContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='windows'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="windows">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (Windows)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC on Windows registers the vlc:// protocol handler during installation. ';tmpHTML+='If it is not working, you can re-register it by saving and running the registry file below.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option A: Reinstall VLC</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Reinstall VLC and ensure "Register VLC as handler for vlc:// protocol" is checked during installation.</div>';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option B: Registry File</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Save this as <code>vlc-protocol.reg</code> and double-click to import. ';tmpHTML+='Adjust the VLC path if yours differs.';tmpHTML+='</div>';let tmpRegFile=this._getWindowsRegFile();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpRegFile)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyWindowsReg()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option C: Batch Script</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Alternatively, save this as <code>vlc-protocol-setup.bat</code> and run as Administrator. ';tmpHTML+='This creates a wrapper script that URL-decodes the vlc:// link before passing it to VLC.';tmpHTML+='</div>';let tmpBatchScript=this._getWindowsBatchScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpBatchScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyWindowsBatch()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildLinuxContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='linux'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="linux">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (Linux)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Register a vlc:// protocol handler using a .desktop file and xdg-mime. ';tmpHTML+='Run the command below in a terminal.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup Command</div>';let tmpScript=this._getLinuxSetupScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyLinuxSetup()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">What This Does</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates a handler script at <code>~/.local/bin/vlc-protocol</code> that URL-decodes and opens VLC</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates a .desktop file at <code>~/.local/share/applications/vlc-protocol.desktop</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">3</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Registers vlc:// as a URL scheme via <code>xdg-mime</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">Requires VLC and Python 3 installed.</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_getMacSetupScript(){return["# Create the AppleScript handler","cat > /tmp/VLCProtocol.applescript << 'EOF'","on open location theURL","\tset theURL to text 7 thru -1 of theURL","\tset theURL to do shell script \"python3 -c 'import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))' \" & quoted form of theURL","\tdo shell script \"open -a VLC \" & quoted form of theURL","end open location","EOF","","# Compile into app bundle","osacompile -o /Applications/VLCProtocol.app /tmp/VLCProtocol.applescript","","# Add vlc:// URL scheme to Info.plist","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes array\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0 dict\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLName string 'VLC Protocol'\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes array\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string vlc\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","","# Register with Launch Services","/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \\"," -f /Applications/VLCProtocol.app","","echo \"VLC protocol handler installed successfully.\""].join('\n');}_getWindowsRegFile(){return["Windows Registry Editor Version 5.00","","[HKEY_CLASSES_ROOT\\vlc]","@=\"URL:VLC Protocol\"","\"URL Protocol\"=\"\"","","[HKEY_CLASSES_ROOT\\vlc\\shell]","","[HKEY_CLASSES_ROOT\\vlc\\shell\\open]","","[HKEY_CLASSES_ROOT\\vlc\\shell\\open\\command]","@=\"\\\"C:\\\\Program Files\\\\VideoLAN\\\\VLC\\\\vlc.exe\\\" \\\"%1\\\"\""].join('\n');}_getWindowsBatchScript(){return["@echo off","REM VLC Protocol Handler Setup for Windows","REM Run this as Administrator","","REM Create the handler script","mkdir \"%APPDATA%\\VLCProtocol\" 2>nul","(","echo import sys, urllib.parse, subprocess","echo url = sys.argv[1] if len(sys.argv^) ^> 1 else ''","echo if url.startswith('vlc://'^): url = url[6:]","echo url = urllib.parse.unquote(url^)","echo subprocess.Popen(['C:\\\\Program Files\\\\VideoLAN\\\\VLC\\\\vlc.exe', url]^)",") > \"%APPDATA%\\VLCProtocol\\handler.py\"","","REM Register the protocol in the registry","reg add \"HKCU\\Software\\Classes\\vlc\" /ve /d \"URL:VLC Protocol\" /f","reg add \"HKCU\\Software\\Classes\\vlc\" /v \"URL Protocol\" /d \"\" /f","reg add \"HKCU\\Software\\Classes\\vlc\\shell\\open\\command\" /ve /d \"pythonw \\\"%APPDATA%\\VLCProtocol\\handler.py\\\" \\\"%%1\\\"\" /f","","echo VLC protocol handler installed successfully.","pause"].join('\n');}_getLinuxSetupScript(){return["# Create handler script","mkdir -p ~/.local/bin","cat > ~/.local/bin/vlc-protocol << 'EOF'","#!/bin/bash","URL=\"$1\"","URL=\"${URL#vlc://}\"","URL=$(python3 -c \"import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))\" \"$URL\")","exec vlc \"$URL\" &","EOF","chmod +x ~/.local/bin/vlc-protocol","","# Create .desktop file","cat > ~/.local/share/applications/vlc-protocol.desktop << 'EOF'","[Desktop Entry]","Name=VLC Protocol Handler","Exec=bash -c '~/.local/bin/vlc-protocol %u'","Type=Application","NoDisplay=true","MimeType=x-scheme-handler/vlc;","EOF","","# Register the handler","xdg-mime default vlc-protocol.desktop x-scheme-handler/vlc","update-desktop-database ~/.local/share/applications/","","echo \"VLC protocol handler installed successfully.\""].join('\n');}_copyToClipboard(pText,pLabel){if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(pText).then(()=>{this.pict.providers['RetoldRemote-ToastNotification'].showToast(pLabel+' copied to clipboard');}).catch(()=>{this._fallbackCopy(pText,pLabel);});}else{this._fallbackCopy(pText,pLabel);}}_fallbackCopy(pText,pLabel){let tmpTextarea=document.createElement('textarea');tmpTextarea.value=pText;tmpTextarea.style.position='fixed';tmpTextarea.style.left='-9999px';document.body.appendChild(tmpTextarea);tmpTextarea.select();try{document.execCommand('copy');this.pict.providers['RetoldRemote-ToastNotification'].showToast(pLabel+' copied to clipboard');}catch(pErr){this.pict.providers['RetoldRemote-ToastNotification'].showToast('Failed to copy - please select and copy manually');}document.body.removeChild(tmpTextarea);}copyMacSetup(){this._copyToClipboard(this._getMacSetupScript(),'macOS setup script');}copyWindowsReg(){this._copyToClipboard(this._getWindowsRegFile(),'Registry file');}copyWindowsBatch(){this._copyToClipboard(this._getWindowsBatchScript(),'Batch script');}copyLinuxSetup(){this._copyToClipboard(this._getLinuxSetupScript(),'Linux setup script');}testProtocol(){let tmpIsWindows=/Windows/.test(navigator.userAgent);let tmpIsMobile=/iPhone|iPad|iPod|Android/i.test(navigator.userAgent);let tmpSampleURL='https://www.sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4';let tmpTestURL=tmpIsWindows||tmpIsMobile?'vlc://'+tmpSampleURL:'vlc://'+encodeURIComponent(tmpSampleURL);let tmpLink=document.createElement('a');tmpLink.href=tmpTestURL;tmpLink.style.display='none';document.body.appendChild(tmpLink);tmpLink.click();document.body.removeChild(tmpLink);}}RetoldRemoteVLCSetupView.default_configuration=_ViewConfiguration;module.exports=RetoldRemoteVLCSetupView;},{"pict-view":76}],147:[function(require,module,exports){const libPictView=require('pict-view');const _VideoExplorerSelection=require('./VideoExplorer-Selection');const _VideoExplorerCustomFrames=require('./VideoExplorer-CustomFrames');const _VideoExplorerPreview=require('./VideoExplorer-Preview');const _ViewConfiguration={ViewIdentifier:"RetoldRemote-VideoExplorer",DefaultRenderable:"RetoldRemote-VideoExplorer",DefaultDestinationAddress:"#RetoldRemote-Viewer-Container",AutoRender:false,CSS:``};class RetoldRemoteVideoExplorerView extends libPictView{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);this._currentPath='';this._frameData=null;this._selectedFrameIndex=-1;this._frameCount=20;this._fullResFrames=true;this._customFrames=[];// Selection mode and state for timeline range selection
11352
+ tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Test</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Click below to test whether the vlc:// protocol handler is registered. VLC should open.';tmpHTML+='</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn" onclick="pict.views[\'RetoldRemote-VLCSetup\'].testProtocol()">Test VLC Protocol</button>';tmpHTML+='</div>';tmpContainer.innerHTML=tmpHTML;}_buildPlatformTab(pKey,pLabel,pActive){let tmpClass='retold-remote-vlc-setup-platform-tab';if(pKey===pActive){tmpClass+=' active';}return'<button class="'+tmpClass+'" onclick="pict.views[\'RetoldRemote-VLCSetup\'].switchPlatformTab(\''+pKey+'\')">'+pLabel+'</button>';}_getPlatformLabel(pKey){if(pKey==='ios')return'iOS';if(pKey==='android')return'Android';if(pKey==='macos')return'macOS';if(pKey==='windows')return'Windows';return'Linux';}_buildIOSContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='ios'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="ios">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (iOS)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC for iOS registers the vlc:// protocol handler automatically when installed. No additional setup is needed.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Installation</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Install <b>VLC for Mobile</b> from the App Store if you haven\'t already.</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Tap the <b>Stream with VLC</b> button on any video or audio file. Safari will ask to open VLC — tap <b>Open</b>.</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">If Safari shows "Cannot Open Page", VLC may not be installed or may need to be updated.</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildAndroidContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='android'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="android">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (Android)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC for Android registers the vlc:// protocol handler automatically when installed. No additional setup is needed.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Installation</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Install <b>VLC for Android</b> from the Google Play Store if you haven\'t already.</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Tap the <b>Stream with VLC</b> button on any video or audio file. Your browser will ask to open VLC — tap <b>Open</b>.</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">If your browser shows an error, VLC may not be installed or may need to be updated.</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildMacOSContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='macos'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="macos">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (macOS)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='VLC on macOS does not register a vlc:// protocol handler by default. ';tmpHTML+='An AppleScript app bundle is needed to bridge vlc:// links to VLC. ';tmpHTML+='Run the command below in Terminal to create and register the handler automatically.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Automatic Setup</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Copy and paste this into Terminal:';tmpHTML+='</div>';let tmpScript=this._getMacSetupScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyMacSetup()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">What This Does</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates an AppleScript at <code>/tmp/VLCProtocol.applescript</code> that handles vlc:// URLs</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Compiles it into an app bundle at <code>/Applications/VLCProtocol.app</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">3</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Adds the vlc:// URL scheme to the app\'s Info.plist</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">4</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Registers the protocol handler with macOS Launch Services</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">Requires VLC installed at /Applications/VLC.app and Python 3 (included with macOS).</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildWindowsContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='windows'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="windows">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (Windows)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Windows requires a protocol handler to open vlc:// links. ';tmpHTML+='Choose one of the options below to register the handler.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option A: Reinstall VLC</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Reinstall VLC and ensure "Register VLC as handler for vlc:// protocol" is checked during installation.</div>';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option B: Registry File</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Save this as <code>vlc-protocol.reg</code> and double-click to import. ';tmpHTML+='Adjust the VLC path if yours differs.';tmpHTML+='</div>';let tmpRegFile=this._getWindowsRegFile();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpRegFile)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyWindowsReg()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Option C: Batch Script</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Alternatively, save this as <code>vlc-protocol-setup.bat</code> and run as Administrator. ';tmpHTML+='This creates a handler script and registers the vlc:// protocol automatically.';tmpHTML+='</div>';let tmpBatchScript=this._getWindowsBatchScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpBatchScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyWindowsBatch()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_buildLinuxContent(pActive){let tmpClass='retold-remote-vlc-setup-platform'+(pActive==='linux'?' active':'');let tmpHTML='<div class="'+tmpClass+'" data-platform="linux">';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup (Linux)</div>';tmpHTML+='<div class="retold-remote-vlc-setup-desc">';tmpHTML+='Register a vlc:// protocol handler using a .desktop file and xdg-mime. ';tmpHTML+='Run the command below in a terminal.';tmpHTML+='</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">Setup Command</div>';let tmpScript=this._getLinuxSetupScript();tmpHTML+='<div class="retold-remote-vlc-setup-code">'+this.pict.providers['RetoldRemote-FormattingUtilities'].escapeHTML(tmpScript)+'</div>';tmpHTML+='<button class="retold-remote-vlc-setup-btn primary" onclick="pict.views[\'RetoldRemote-VLCSetup\'].copyLinuxSetup()">Copy to Clipboard</button>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-section">';tmpHTML+='<div class="retold-remote-vlc-setup-section-title">What This Does</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">1</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates a handler script at <code>~/.local/bin/vlc-protocol</code> that URL-decodes and opens VLC</div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">2</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Creates a .desktop file at <code>~/.local/share/applications/vlc-protocol.desktop</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step">';tmpHTML+='<div class="retold-remote-vlc-setup-step-num">3</div>';tmpHTML+='<div class="retold-remote-vlc-setup-step-content">Registers vlc:// as a URL scheme via <code>xdg-mime</code></div>';tmpHTML+='</div>';tmpHTML+='<div class="retold-remote-vlc-setup-note">Requires VLC and Python 3 installed.</div>';tmpHTML+='</div>';tmpHTML+='</div>';return tmpHTML;}_getMacSetupScript(){return["# Create the AppleScript handler","cat > /tmp/VLCProtocol.applescript << 'EOF'","on open location theURL","\tset theURL to text 7 thru -1 of theURL","\tset theURL to do shell script \"python3 -c 'import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))' \" & quoted form of theURL","\tdo shell script \"open -a VLC \" & quoted form of theURL","end open location","EOF","","# Compile into app bundle","osacompile -o /Applications/VLCProtocol.app /tmp/VLCProtocol.applescript","","# Add vlc:// URL scheme to Info.plist","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes array\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0 dict\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLName string 'VLC Protocol'\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes array\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","/usr/libexec/PlistBuddy -c \"Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string vlc\" \\"," /Applications/VLCProtocol.app/Contents/Info.plist 2>/dev/null","","# Register with Launch Services","/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister \\"," -f /Applications/VLCProtocol.app","","echo \"VLC protocol handler installed successfully.\""].join('\n');}_getWindowsRegFile(){// Use an inline PowerShell command to strip the vlc:// prefix and
11353
+ // URL-decode before launching VLC. The URL is percent-encoded by
11354
+ // the client to prevent Windows from stripping the colon in nested
11355
+ // http:// URLs (a known Windows protocol handler issue).
11356
+ return["Windows Registry Editor Version 5.00","","[HKEY_CLASSES_ROOT\\vlc]","@=\"URL:VLC Protocol\"","\"URL Protocol\"=\"\"","","[HKEY_CLASSES_ROOT\\vlc\\shell]","","[HKEY_CLASSES_ROOT\\vlc\\shell\\open]","","[HKEY_CLASSES_ROOT\\vlc\\shell\\open\\command]","@=\"powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -Command \\\"$u='%1'; if($u.StartsWith('vlc://')){$u=$u.Substring(6)}; $u=[System.Uri]::UnescapeDataString($u); $u=$u.TrimEnd('/'); Start-Process -FilePath 'C:\\\\Program Files\\\\VideoLAN\\\\VLC\\\\vlc.exe' -ArgumentList $u\\\"\""].join('\n');}_getWindowsBatchScript(){// Creates a PowerShell handler script and registers the vlc://
11357
+ // protocol to use it. The handler URL-decodes the argument because
11358
+ // the client percent-encodes the URL to prevent Windows from
11359
+ // stripping colons in nested http:// URLs.
11360
+ return["@echo off","REM VLC Protocol Handler Setup for Windows","REM Run this as Administrator","","REM Create the handler directory","mkdir \"%APPDATA%\\VLCProtocol\" 2>nul","","REM Write the PowerShell handler script","(","echo $url = $args[0]","echo if ^($url -and $url.StartsWith^('vlc://'^)^) { $url = $url.Substring^(6^) }","echo $url = [System.Uri]::UnescapeDataString^($url^)","echo $url = $url.TrimEnd^('/'^)","echo if ^($url^) { Start-Process 'C:\\Program Files\\VideoLAN\\VLC\\vlc.exe' -ArgumentList $url }",") > \"%APPDATA%\\VLCProtocol\\handler.ps1\"","","REM Register the protocol in the registry","reg add \"HKCU\\Software\\Classes\\vlc\" /ve /d \"URL:VLC Protocol\" /f","reg add \"HKCU\\Software\\Classes\\vlc\" /v \"URL Protocol\" /d \"\" /f","reg add \"HKCU\\Software\\Classes\\vlc\\shell\\open\\command\" /ve /d \"powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File \\\"%APPDATA%\\VLCProtocol\\handler.ps1\\\" \\\"%%1\\\"\" /f","","echo VLC protocol handler installed successfully.","pause"].join('\n');}_getLinuxSetupScript(){return["# Create handler script","mkdir -p ~/.local/bin","cat > ~/.local/bin/vlc-protocol << 'EOF'","#!/bin/bash","URL=\"$1\"","URL=\"${URL#vlc://}\"","URL=$(python3 -c \"import sys, urllib.parse; print(urllib.parse.unquote(sys.argv[1]))\" \"$URL\")","exec vlc \"$URL\" &","EOF","chmod +x ~/.local/bin/vlc-protocol","","# Create .desktop file","cat > ~/.local/share/applications/vlc-protocol.desktop << 'EOF'","[Desktop Entry]","Name=VLC Protocol Handler","Exec=bash -c '~/.local/bin/vlc-protocol %u'","Type=Application","NoDisplay=true","MimeType=x-scheme-handler/vlc;","EOF","","# Register the handler","xdg-mime default vlc-protocol.desktop x-scheme-handler/vlc","update-desktop-database ~/.local/share/applications/","","echo \"VLC protocol handler installed successfully.\""].join('\n');}_copyToClipboard(pText,pLabel){if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(pText).then(()=>{this.pict.providers['RetoldRemote-ToastNotification'].showToast(pLabel+' copied to clipboard');}).catch(()=>{this._fallbackCopy(pText,pLabel);});}else{this._fallbackCopy(pText,pLabel);}}_fallbackCopy(pText,pLabel){let tmpTextarea=document.createElement('textarea');tmpTextarea.value=pText;tmpTextarea.style.position='fixed';tmpTextarea.style.left='-9999px';document.body.appendChild(tmpTextarea);tmpTextarea.select();try{document.execCommand('copy');this.pict.providers['RetoldRemote-ToastNotification'].showToast(pLabel+' copied to clipboard');}catch(pErr){this.pict.providers['RetoldRemote-ToastNotification'].showToast('Failed to copy - please select and copy manually');}document.body.removeChild(tmpTextarea);}copyMacSetup(){this._copyToClipboard(this._getMacSetupScript(),'macOS setup script');}copyWindowsReg(){this._copyToClipboard(this._getWindowsRegFile(),'Registry file');}copyWindowsBatch(){this._copyToClipboard(this._getWindowsBatchScript(),'Batch script');}copyLinuxSetup(){this._copyToClipboard(this._getLinuxSetupScript(),'Linux setup script');}testProtocol(){let tmpIsWindows=/Windows/.test(navigator.userAgent);let tmpIsMobile=/iPhone|iPad|iPod|Android/i.test(navigator.userAgent);let tmpSampleURL='https://www.sample-videos.com/video321/mp4/720/big_buck_bunny_720p_1mb.mp4';let tmpTestURL=tmpIsWindows||tmpIsMobile?'vlc://'+tmpSampleURL:'vlc://'+encodeURIComponent(tmpSampleURL);let tmpLink=document.createElement('a');tmpLink.href=tmpTestURL;tmpLink.style.display='none';document.body.appendChild(tmpLink);tmpLink.click();document.body.removeChild(tmpLink);}}RetoldRemoteVLCSetupView.default_configuration=_ViewConfiguration;module.exports=RetoldRemoteVLCSetupView;},{"pict-view":76}],147:[function(require,module,exports){const libPictView=require('pict-view');const _VideoExplorerSelection=require('./VideoExplorer-Selection');const _VideoExplorerCustomFrames=require('./VideoExplorer-CustomFrames');const _VideoExplorerPreview=require('./VideoExplorer-Preview');const _ViewConfiguration={ViewIdentifier:"RetoldRemote-VideoExplorer",DefaultRenderable:"RetoldRemote-VideoExplorer",DefaultDestinationAddress:"#RetoldRemote-Viewer-Container",AutoRender:false,CSS:``};class RetoldRemoteVideoExplorerView extends libPictView{constructor(pFable,pOptions,pServiceHash){super(pFable,pOptions,pServiceHash);this._currentPath='';this._frameData=null;this._selectedFrameIndex=-1;this._frameCount=20;this._fullResFrames=true;this._customFrames=[];// Selection mode and state for timeline range selection
11327
11361
  this._selectionModeActive=false;this._selectionStartTime=-1;this._selectionEndTime=-1;this._isSelectingRange=false;this._isDraggingTimeline=false;this._draggingHandle=null;// 'start', 'end', or null
11328
11362
  // Cached provider references (resolved lazily)
11329
11363
  this._fmt=null;this._provider=null;}// -----------------------------------------------------------------
@@ -11374,7 +11408,7 @@ tmpCollMgr.setPendingClipContext({Type:'video-clip',Start:tmpStart,End:tmpEnd});
11374
11408
  * @param {string} pFilePath - Relative file path
11375
11409
  * @param {number} [pSelectionStart] - Optional selection start time (seconds)
11376
11410
  * @param {number} [pSelectionEnd] - Optional selection end time (seconds)
11377
- */showExplorer(pFilePath,pSelectionStart,pSelectionEnd){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='video-explorer';this._currentPath=pFilePath;this._frameData=null;this._selectedFrameIndex=-1;this._customFrames=[];this._selectionModeActive=false;this._isSelectingRange=false;this._isDraggingTimeline=false;this._draggingHandle=null;// Apply passed-in selection range, or reset
11411
+ */showExplorer(pFilePath,pSelectionStart,pSelectionEnd){let tmpRemote=this.pict.AppData.RetoldRemote;tmpRemote.ActiveMode='video-explorer';tmpRemote.CurrentViewerFile=pFilePath;tmpRemote.CurrentViewerMediaType='video';this._currentPath=pFilePath;this._frameData=null;this._selectedFrameIndex=-1;this._customFrames=[];this._selectionModeActive=false;this._isSelectingRange=false;this._isDraggingTimeline=false;this._draggingHandle=null;// Apply passed-in selection range, or reset
11378
11412
  // _selectionFromCaller prevents _loadSavedCustomFrames from
11379
11413
  // overwriting an explicit selection (e.g. when opening a saved clip)
11380
11414
  if(typeof pSelectionStart==='number'&&pSelectionStart>=0&&typeof pSelectionEnd==='number'&&pSelectionEnd>=0){this._selectionStartTime=pSelectionStart;this._selectionEndTime=pSelectionEnd;this._selectionFromCaller=true;}else{this._selectionStartTime=-1;this._selectionEndTime=-1;this._selectionFromCaller=false;}// Clean up any window-level event listeners from previous session
@@ -11383,7 +11417,7 @@ this._cleanupWindowListeners();// Update the hash. Replace (not push) when comi
11383
11417
  let tmpFragProvider=this._getProvider();let tmpFragId=tmpFragProvider?tmpFragProvider.getFragmentIdentifier(pFilePath):pFilePath;let tmpNewHash='#/explore/'+tmpFragId;let tmpCurrentHash=window.location.hash||'';if(tmpCurrentHash.indexOf('#/view/')===0){history.replaceState(null,'',tmpNewHash);}else{window.location.hash=tmpNewHash;}// Show viewer container, hide gallery
11384
11418
  let tmpGalleryContainer=document.getElementById('RetoldRemote-Gallery-Container');let tmpViewerContainer=document.getElementById('RetoldRemote-Viewer-Container');if(tmpGalleryContainer)tmpGalleryContainer.style.display='none';if(tmpViewerContainer)tmpViewerContainer.style.display='block';let tmpFileName=pFilePath.replace(/^.*\//,'');// Build initial UI with loading state
11385
11419
  let tmpHTML='<div class="retold-remote-vex">';// Header
11386
- tmpHTML+='<div class="retold-remote-vex-header">';tmpHTML+='<button class="retold-remote-vex-nav-btn" onclick="pict.views[\'RetoldRemote-VideoExplorer\'].goBack()" title="Back to video (Esc)">&larr; Back</button>';tmpHTML+='<div class="retold-remote-vex-title">Video Explorer &mdash; '+this._getFmt().escapeHTML(tmpFileName)+'</div>';tmpHTML+='</div>';// Info bar (populated after frames load)
11420
+ let tmpCapabilities=tmpRemote.ServerCapabilities||{};tmpHTML+='<div class="retold-remote-vex-header">';tmpHTML+='<button class="retold-remote-vex-nav-btn" onclick="pict.views[\'RetoldRemote-VideoExplorer\'].goBack()" title="Back (Esc)">&larr; Back</button>';tmpHTML+='<div class="retold-remote-vex-title">Video Explorer &mdash; '+this._getFmt().escapeHTML(tmpFileName)+'</div>';tmpHTML+='<div class="retold-remote-vex-actions">';tmpHTML+='<button class="retold-remote-vex-action-btn" onclick="pict.views[\'RetoldRemote-VideoExplorer\'].playInBrowser()" title="Play in browser (Space)">&#9654; Play</button>';tmpHTML+='<button class="retold-remote-vex-action-btn" onclick="pict.providers[\'RetoldRemote-GalleryNavigation\']._streamWithVLC()" title="Stream with VLC (v)">&#9654; VLC</button>';if(tmpCapabilities.ffmpeg||tmpCapabilities.ffprobe){tmpHTML+='<button class="retold-remote-vex-action-btn" onclick="pict.views[\'RetoldRemote-AudioExplorer\'].showExplorer(pict.views[\'RetoldRemote-VideoExplorer\']._currentPath)" title="Explore audio track">&#9835; Audio</button>';}tmpHTML+='</div>';tmpHTML+='</div>';// Info bar (populated after frames load)
11387
11421
  tmpHTML+='<div class="retold-remote-vex-info" id="RetoldRemote-VEX-Info" style="display:none;"></div>';// Controls bar
11388
11422
  tmpHTML+='<div class="retold-remote-vex-controls" id="RetoldRemote-VEX-Controls" style="display:none;">';tmpHTML+='<label>Frames:</label>';tmpHTML+='<select id="RetoldRemote-VEX-FrameCount" onchange="pict.views[\'RetoldRemote-VideoExplorer\'].onFrameCountChange(this.value)">';tmpHTML+='<option value="10"'+(this._frameCount===10?' selected':'')+'>10</option>';tmpHTML+='<option value="20"'+(this._frameCount===20?' selected':'')+'>20</option>';tmpHTML+='<option value="40"'+(this._frameCount===40?' selected':'')+'>40</option>';tmpHTML+='<option value="60"'+(this._frameCount===60?' selected':'')+'>60</option>';tmpHTML+='<option value="100"'+(this._frameCount===100?' selected':'')+'>100</option>';tmpHTML+='</select>';tmpHTML+='<label style="display:inline-flex;align-items:center;gap:4px;cursor:pointer;">';tmpHTML+='<input type="checkbox" id="RetoldRemote-VEX-FullRes"'+(this._fullResFrames?' checked':'')+' onchange="pict.views[\'RetoldRemote-VideoExplorer\'].onFullResChange(this.checked)">';tmpHTML+='Full Res Frames</label>';tmpHTML+='<button class="retold-remote-vex-refresh-btn" onclick="pict.views[\'RetoldRemote-VideoExplorer\'].refresh()">Refresh</button>';tmpHTML+='<span style="border-left:1px solid var(--retold-border);height:20px;margin:0 4px;"></span>';tmpHTML+='<button class="retold-remote-vex-select-btn" id="RetoldRemote-VEX-SelectBtn" onclick="pict.views[\'RetoldRemote-VideoExplorer\'].toggleSelectionMode()">Select Range</button>';tmpHTML+='<span class="retold-remote-vex-selection-info" id="RetoldRemote-VEX-SelectionInfo" style="display:none;"></span>';tmpHTML+='<button class="retold-remote-vex-clear-btn" id="RetoldRemote-VEX-ClearBtn" style="display:none;" onclick="pict.views[\'RetoldRemote-VideoExplorer\'].clearSelection()">Clear</button>';tmpHTML+='<span id="RetoldRemote-VEX-GenerateControls" style="display:none;">';tmpHTML+='<span style="border-left:1px solid var(--retold-border);height:20px;margin:0 2px;"></span>';tmpHTML+='<select class="retold-remote-vex-range-frame-select" id="RetoldRemote-VEX-RangeFrameCount">';tmpHTML+='<option value="3">3</option>';tmpHTML+='<option value="5" selected>5</option>';tmpHTML+='<option value="10">10</option>';tmpHTML+='<option value="20">20</option>';tmpHTML+='</select>';tmpHTML+='<button class="retold-remote-vex-generate-btn" onclick="pict.views[\'RetoldRemote-VideoExplorer\'].generateSelectionFrames()">Generate Frames</button>';tmpHTML+='<span style="border-left:1px solid var(--retold-border);height:20px;margin:0 2px;"></span>';tmpHTML+='<button class="retold-remote-vex-save-btn" onclick="pict.views[\'RetoldRemote-VideoExplorer\'].saveSelectionToCollection()" title="Save segment to collection (s)">Save Segment</button>';tmpHTML+='</span>';tmpHTML+='</div>';// Body (loading initially)
11389
11423
  tmpHTML+='<div class="retold-remote-vex-body" id="RetoldRemote-VEX-Body">';tmpHTML+='<div class="retold-remote-vex-loading">';tmpHTML+='<div class="retold-remote-vex-loading-spinner"></div>';tmpHTML+='Extracting frames from video...';tmpHTML+='</div>';tmpHTML+='</div>';// Timeline bar (populated after frames load)
@@ -11450,8 +11484,10 @@ let tmpTimeline=document.getElementById('RetoldRemote-VEX-Timeline');if(tmpTimel
11450
11484
  // Navigation
11451
11485
  // -----------------------------------------------------------------
11452
11486
  /**
11453
- * Navigate back to the video viewer.
11454
- */goBack(){this._cleanupWindowListeners();if(this._currentPath){let tmpApp=this.pict.views['RetoldRemote-MediaViewer'];if(tmpApp){tmpApp.showMedia(this._currentPath,'video');}}else{let tmpNav=this.pict.providers['RetoldRemote-GalleryNavigation'];if(tmpNav){tmpNav.closeViewer();}}}/**
11487
+ * Navigate back to the gallery / file listing.
11488
+ */goBack(){this._cleanupWindowListeners();let tmpNav=this.pict.providers['RetoldRemote-GalleryNavigation'];if(tmpNav){tmpNav.closeViewer();}}/**
11489
+ * Leave the video explorer and play the video in the browser viewer.
11490
+ */playInBrowser(){this._cleanupWindowListeners();let tmpViewer=this.pict.views['RetoldRemote-MediaViewer'];if(tmpViewer){tmpViewer.showMedia(this._currentPath,'video');tmpViewer.playVideo();}}/**
11455
11491
  * Show an error message.
11456
11492
  *
11457
11493
  * @param {string} pMessage - Error message