Greasy Fork is available in English.

Use video.js in phtn.app

Use video.js player instead default of phtn.app/browser

// ==UserScript==
// @name            Use video.js in phtn.app
// @namespace       https://greasyfork.org/users/821661
// @match           https://phtn.app/*
// @grant           GM_addElement
// @grant           GM_addStyle
// @grant           GM_getResourceText
// @require         https://cdnjs.cloudflare.com/ajax/libs/video.js/8.10.0/video.min.js
// @resource        videoJsCss https://cdnjs.cloudflare.com/ajax/libs/video.js/8.10.0/video-js.min.css
// @version         1.0
// @author          hdyzen
// @description     Use video.js player instead default of phtn.app/browser
// @license         MIT
// ==/UserScript==
'use strict';

// Detect videos elements every 1s and handle them
async function detectVideos() {
    while (true) {
        await new Promise(r => setTimeout(r, 1000));
        const videos = document.querySelectorAll('video.rounded-xl.aspect-video:not([data-vjs]');
        videos.forEach(videoHandler);
    }
}
detectVideos();

// Handle video: add data-vjs, create video.js, call videojs()
function videoHandler(video) {
    video.setAttribute('data-vjs', '');
    video.classList.add('video-js');
    video.classList.add('vjs-matrix');

    const src = video.currentSrc;

    observer(video);
    const player = videojs(video, {
        sources: src,
        playbackRates: [0.5, 1, 1.5, 2],
    });
}

// Intersection observer on video
function observer(target) {
    const observer = new IntersectionObserver(observeCallback, {
        threshold: 0.8,
    });
    observer.observe(target);
}

// Callback on intersection
function observeCallback(entries) {
    entries.forEach(entry => {
        if (!entry.isIntersecting && !entry.target.paused) entry.target.pause();
    });
}

// Style default video.js
const style = GM_getResourceText('videoJsCss');
GM_addStyle(style);

// Style override
GM_addStyle(`
/* Overrides video.js */
.vjs-matrix.video-js {
    font-size: 13px;
    border-radius: 10px;
    overflow: hidden;
    width: 100%;
    height: 100%;

    /* Before creating fading effect */
    &:not(.vjs-has-started)::before {
        content: '';
        position: absolute;
        inset: 0;
        background-color: rgba(0, 0, 0, 0.25);
        z-index: 1;
        pointer-events: none;
    }

    /* Play button */
    & .vjs-big-play-button {
        border: none;
        background: none;
        font-size: 6em;
        transition: 0.2s ease;
        z-index: 1;
    }
    &:hover .vjs-big-play-button {
        background: none;
        font-size: 6.5em;
    }

    /* Control bar */
    & .vjs-control-bar {
        width: auto;
        inset: auto 10px 10px;
        background-color: rgba(0, 0, 0, 0.3);
        border: 1px solid rgba(0, 0, 0, 0.08);
        border-radius: 0.5em;
        backdrop-filter: blur(10px);

        /* Progress bar */
        & .vjs-slider {
            background-color: rgba(255, 255, 255, 0.2);
            & .vjs-load-progress {
                background-color: rgba(255, 255, 255, 0.2);

                & div {
                    background-color: rgba(255, 255, 255, 0.5);
                }
            }
        }
    }

    /* Menu playbacks */
    & .vjs-menu {
        top: 0;
        bottom: unset;
        
        & .vjs-menu-content {
            bottom: 100%;
            background-color: rgba(0, 0, 0, 0.3);
            border-radius: 0.3em;
        }

        & .vjs-menu-item:not(.vjs-selected):hover {
            background-color: rgba(255, 255, 255, 0.15);
        }

        & .vjs-menu-item.vjs-selected {
            color: rgb(34, 34, 34);
        }
    }

    & .vjs-playback-rate-value {
        font-size: 1.2em;
        line-height: 2.4em;
        vertical-align: text-bottom;
    }
}
video.rounded-xl.aspect-video {
    visibility: hidden;
}
`);