stashapp / stash

An organizer for your porn, written in Go. Documentation: https://docs.stashapp.cc
https://stashapp.cc/
GNU Affero General Public License v3.0
8.97k stars 785 forks source link

[Feature] Set Default live transcode or change priority #4175

Open slimthick opened 1 year ago

slimthick commented 1 year ago

Is your feature request related to a problem? Please describe. My WMV files seek and load faster when using DASH/HLS instead of the default mp4 live transcode

Describe the solution you'd like A way to set the default transcode or reorder the list in the player.

Describe alternatives you've considered I checked the yaml config file as well and couldn't find anything.

slimthick commented 10 months ago

In case anyone else was interested I wrote some custom Javascript to resolve this problem because I couldn't locate the vjsplayer instance on the page so I had to observe it. While writing this I discovered that

  1. To unlock the full power of a 12th gen Intel for transcoding vs QuickSync I need the "non-free" drivers that I was supposed to know about.
  2. At least according to intel_gpu_top, the reason why HLS/DASH/WEBM transcodes are faster is because MP4 doesn't use the integrated GPU. It also doesn't work as hard which means the fan doesn't spin up and the minipc is quieter.
//https://stackoverflow.com/a/17799291
function contains(selector, text) {
  var elements = document.querySelectorAll(selector);
  return [].filter.call(elements, function(element){
    return RegExp(text).test(element.textContent);
  });
}

function useHLS(){
    if( !videoPlayer.canBeStreamed() && !videoPlayer.hlsElementSelected() ){

        window.usingHLS = true;

        videoPlayer.hlsElement().click();

        console.log("Video has to be transcoded, using HLS");

    } 
}

//Observe less often while not on the scene player because hundreds of observations can occur
//and any observation can confirm if the url contains /scenes/
function delayObservation(delay){
    urlObserver.disconnect();

    setTimeout(function(){
        urlObserver.observe( document.getElementsByClassName('main container-fluid')[0], urlObserverConfig);
    }, delay);
}

/*------------------------------------------------------------------------------------------------*/

var videoPlayer = {

    videoJsElement : null,

    loadedPlayer:  null,

    isPlaying: function(){ //https://stackoverflow.com/a/36898221
                    return  this.loadedPlayer.currentTime > 0 && 
                            !this.loadedPlayer.paused && 
                            !this.loadedPlayer.ended && 
                            this.loadedPlayer.readyState > this.loadedPlayer.HAVE_CURRENT_DATA;
                },

    canBeStreamed: function(){
        return ( contains('.vjs-menu-item-text', /^direct stream$/i).length > 0 );
    },

    hlsElement: function(){ //optional chaining (?.)
        return ( contains('.vjs-menu-item-text', /^hls$/i)[0]?.parentElement );
    },

    hlsElementSelected: function(){
        return ( this.hlsElement()?.classList.contains('vjs-selected') );
    }
};

var oldScene = '';
var delay = 1000;

/*------------------------------------------------------------------------------------------------*/

const videoPlayerObserverConfig = {attributes: true};

const videoPlayerObserver = new MutationObserver(function(mutations, observer) {

//wait until the video is playing to avoid triggering a "The play() request was interrupted" error
//"It is worth noting that the (play())Promise won't fulfill until playback has actually started" https://developer.chrome.com/blog/play-request-was-interrupted/

    if( videoPlayer.isPlaying() ) { 

        observer.disconnect();      
        useHLS();

    }

});

//https://stackoverflow.com/a/67825703 
//One page application that updates href but doesnt reload the page. Observe when the application gets to
//the scenes player and attach the final observer to default to HLS as needed       

const urlObserverConfig = {childList: true, subtree: true};

var urlObserver = new MutationObserver(function(mutations, observer) {

    var onPlayerPage = location.href.toLocaleLowerCase().indexOf('/scenes/') !== -1;

    if ( onPlayerPage && (!videoPlayer.loadedPlayer) ) {

        videoPlayer.videoJsElement = document.querySelector('#VideoJsPlayer');
        videoPlayer.loadedPlayer = document.querySelector('#VideoJsPlayer_html5_api');

    }

    if ( onPlayerPage && videoPlayer.loadedPlayer ){

        var currentScene = location.href.match(/.*\/scenes\/\d+/i)[0];

    // /scenes? is the scene list while /scenes/{number}? is the scene player   
        if( currentScene !== oldScene){
            oldScene = currentScene;
            videoPlayerObserver.observe( videoPlayer.videoJsElement, videoPlayerObserverConfig );
        }

        delayObservation(delay);
    }

    if( !onPlayerPage ){

        if(videoPlayer.loadedPlayer){
            videoPlayer.loadedPlayer = null;
            videoPlayer.videoJsElement = null;
        }

        delayObservation(delay);
    }
});

//Only existing elements can be observed and the root element is always present
//The content of the application is then housed and updated within main
new MutationObserver(function(mutations, observer) {

    observer.disconnect();

    urlObserver.observe( document.getElementsByClassName('main container-fluid')[0], urlObserverConfig);

}).observe( document.getElementById('root'), {childList: true});
tonynca commented 1 week ago

This is from user PB Stash on discord channel:

It's should just be a matter of updating the following logic in ScenePlayer.tsx:


    const sourceSelector = player.sourceSelector();
    sourceSelector.setSources(
      scene.sceneStreams
        .filter((stream) => {
          const src = new URL(stream.url);
          const isFileTranscode = !isDirect(src);

          return !(isFileTranscode && isSafari);
        })
        .map((stream) => {
          const src = new URL(stream.url);

          return {
            src: stream.url,
            type: stream.mime_type ?? undefined,
            label: stream.label ?? undefined,
            offset: !isDirect(src),
            duration,
          };
        })
    );```

to filter the unsupported sources for Apple devices. The `isPlatformUniquelyRenderedByApple()` util method reports apple devices.