futo-org / grayjay-android

Read-only mirror of Grayjay repo for issue tracking
https://gitlab.futo.org/videostreaming/grayjay
Other
748 stars 48 forks source link

Missing documentation for custom request executor #1413

Open VoxelPrismatic opened 1 week ago

VoxelPrismatic commented 1 week ago

Affected Pages

n/a

What is the docs issue?

There is no documentation for implementing a custom stream format, as seen in the YouTube plugin: https://gitlab.futo.org/videostreaming/plugins/youtube/-/blob/master/YoutubeScript.js?ref_type=heads#L1960

Therefore, when trying to implement a custom HLS executor that would decode HLS segments, I have no clue how to interface with Grayjay.

Proposal

Document it.

References

See https://github.com/voxelprismatic/grayjay-floatplane for my plugin and developer testing

VoxelPrismatic commented 1 week ago

Without the custom getRequestExectutor in my plugin, grayjay throws a toast "Block detected, attempting bypass"

However, I cannot hijack this getRequestExecutor such that executeRequest is called for every segment. It is only ever called to download the initial M3U8 manifest, and not for any of the segments.

VoxelPrismatic commented 1 week ago

https://github.com/user-attachments/assets/d4391207-6e56-465a-9457-db244b3cae5a

[INFO]
Call [requestExecutor.executeRequest()] succesful [104ms] 
[INFO]
EXECUTOR! ~ executeRequest: https://cdn-vod-drm2.floatplane.com/Videos/skJTMsWmgd/720.mp4/chunk.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZVBhdGgiOiIvVmlkZW9zL3NrSlRNc1dtZ2QvNzIwLm1wNC9jaHVuay5tM3U4IiwicmVzc291cmNlUGF0aCI6Ii9WaWRlb3Mvc2tKVE1zV21nZC83MjAubXA0L2NodW5rLm0zdTgiLCJ1c2VySWQiOiI1YzNlNGU2ZDdhM2YwNjI0MjdkMzQ5MjAiLCJpYXQiOjE3MjkxOTkwMDAsImV4cCI6MTcyOTIyMDYwMH0.CTu2x2xnm8lJeXsL95K4RsKcBfOzOiZZ6PwiJlzbukA
[INFO]
Call [requestExecutor.executeRequest()] succesful [72ms] 
[INFO]
EXECUTOR! ~ executeRequest: https://cdn-vod-drm2.floatplane.com/Videos/skJTMsWmgd/480.mp4/chunk.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZVBhdGgiOiIvVmlkZW9zL3NrSlRNc1dtZ2QvNDgwLm1wNC9jaHVuay5tM3U4IiwicmVzc291cmNlUGF0aCI6Ii9WaWRlb3Mvc2tKVE1zV21nZC80ODAubXA0L2NodW5rLm0zdTgiLCJ1c2VySWQiOiI1YzNlNGU2ZDdhM2YwNjI0MjdkMzQ5MjAiLCJpYXQiOjE3MjkxOTkwMDAsImV4cCI6MTcyOTIyMDYwMH0.YauQiaDr3fO525yixUUQAzeOAzlo9svaCIj51b-vte0
[INFO]
Call [requestExecutor.executeRequest()] succesful [60ms] 
[INFO]
EXECUTOR! ~ executeRequest: https://cdn-vod-drm2.floatplane.com/Videos/skJTMsWmgd/360.mp4/chunk.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZVBhdGgiOiIvVmlkZW9zL3NrSlRNc1dtZ2QvMzYwLm1wNC9jaHVuay5tM3U4IiwicmVzc291cmNlUGF0aCI6Ii9WaWRlb3Mvc2tKVE1zV21nZC8zNjAubXA0L2NodW5rLm0zdTgiLCJ1c2VySWQiOiI1YzNlNGU2ZDdhM2YwNjI0MjdkMzQ5MjAiLCJpYXQiOjE3MjkxOTkwMDAsImV4cCI6MTcyOTIyMDYwMH0.Oz-ae4wc6Wwfi94jaYFy_wckuwaKjFs9kfGRGXBL9DY
[INFO]
Call [isVideoDetailsUrl("https://www.floatplane.com/post/l64I6dJA1X")] succesful [0ms] 
[INFO]
Call [isVideoDetailsUrl("https://www.floatplane.com/post/l64I6dJA1X")] succesful [0ms] 
[INFO]
Call [isVideoDetailsUrl("https://www.floatplane.com/post/l64I6dJA1X")] succesful [0ms] 
[INFO]
Call [isVideoDetailsUrl("https://www.floatplane.com/post/l64I6dJA1X")] succesful [0ms] 
[INFO]
Call [getVideoDetails] succesful [923ms] 
[INFO]
[
    {
        "plugin_type": "HLSSource",
        "name": "#1a=360p - I bought 2700 Compact Discs. *Floatplane edit*",
        "duration": 1101,
        "url": "https://cdn-vod-drm2.floatplane.com/Videos/skJTMsWmgd/360.mp4/chunk.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZVBhdGgiOiIvVmlkZW9zL3NrSlRNc1dtZ2QvMzYwLm1wNC9jaHVuay5tM3U4IiwicmVzc291cmNlUGF0aCI6Ii9WaWRlb3Mvc2tKVE1zV21nZC8zNjAubXA0L2NodW5rLm0zdTgiLCJ1c2VySWQiOiI1YzNlNGU2ZDdhM2YwNjI0MjdkMzQ5MjAiLCJpYXQiOjE3MjkxOTkwMDAsImV4cCI6MTcyOTIyMDYwMH0.Oz-ae4wc6Wwfi94jaYFy_wckuwaKjFs9kfGRGXBL9DY",
        "priority": false
    },
    {
        "plugin_type": "HLSSource",
        "name": "#1a=480p - I bought 2700 Compact Discs. *Floatplane edit*",
        "duration": 1101,
        "url": "https://cdn-vod-drm2.floatplane.com/Videos/skJTMsWmgd/480.mp4/chunk.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZVBhdGgiOiIvVmlkZW9zL3NrSlRNc1dtZ2QvNDgwLm1wNC9jaHVuay5tM3U4IiwicmVzc291cmNlUGF0aCI6Ii9WaWRlb3Mvc2tKVE1zV21nZC80ODAubXA0L2NodW5rLm0zdTgiLCJ1c2VySWQiOiI1YzNlNGU2ZDdhM2YwNjI0MjdkMzQ5MjAiLCJpYXQiOjE3MjkxOTkwMDAsImV4cCI6MTcyOTIyMDYwMH0.YauQiaDr3fO525yixUUQAzeOAzlo9svaCIj51b-vte0",
        "priority": false
    },
    {
        "plugin_type": "HLSSource",
        "name": "#1a=720p - I bought 2700 Compact Discs. *Floatplane edit*",
        "duration": 1101,
        "url": "https://cdn-vod-drm2.floatplane.com/Videos/skJTMsWmgd/720.mp4/chunk.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZVBhdGgiOiIvVmlkZW9zL3NrSlRNc1dtZ2QvNzIwLm1wNC9jaHVuay5tM3U4IiwicmVzc291cmNlUGF0aCI6Ii9WaWRlb3Mvc2tKVE1zV21nZC83MjAubXA0L2NodW5rLm0zdTgiLCJ1c2VySWQiOiI1YzNlNGU2ZDdhM2YwNjI0MjdkMzQ5MjAiLCJpYXQiOjE3MjkxOTkwMDAsImV4cCI6MTcyOTIyMDYwMH0.CTu2x2xnm8lJeXsL95K4RsKcBfOzOiZZ6PwiJlzbukA",
        "priority": false
    },
    {
        "plugin_type": "HLSSource",
        "name": "#1a=1080p - I bought 2700 Compact Discs. *Floatplane edit*",
        "duration": 1101,
        "url": "https://cdn-vod-drm2.floatplane.com/Videos/skJTMsWmgd/1080.mp4/chunk.m3u8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZXNvdXJjZVBhdGgiOiIvVmlkZW9zL3NrSlRNc1dtZ2QvMTA4MC5tcDQvY2h1bmsubTN1OCIsInJlc3NvdXJjZVBhdGgiOiIvVmlkZW9zL3NrSlRNc1dtZ2QvMTA4MC5tcDQvY2h1bmsubTN1OCIsInVzZXJJZCI6IjVjM2U0ZTZkN2EzZjA2MjQyN2QzNDkyMCIsImlhdCI6MTcyOTE5OTAwMCwiZXhwIjoxNzI5MjIwNjAwfQ.trmtllAemWOmVRA-pv1dFoTEek2CqKtlbJ_6Cav4jZg",
        "priority": false
    }
]
[INFO]
EXECUTOR! ~ constructor
[INFO]
https://www.floatplane.com/api/video/watchKey?token=eyJwYXRoIjoiL1ZpZGVvcy9za0pUTXNXbWdkLzEwODAubXA0IiwidGltZXN0YW1wIjoxNzI5MTIzMjAwMDAwfQ
[INFO]
EXECUTOR! ~ constructor
[INFO]
https://www.floatplane.com/api/video/watchKey?token=eyJwYXRoIjoiL1ZpZGVvcy9za0pUTXNXbWdkLzcyMC5tcDQiLCJ0aW1lc3RhbXAiOjE3MjkxMjMyMDAwMDB9
[INFO]
EXECUTOR! ~ constructor
[INFO]
https://www.floatplane.com/api/video/watchKey?token=eyJwYXRoIjoiL1ZpZGVvcy9za0pUTXNXbWdkLzQ4MC5tcDQiLCJ0aW1lc3RhbXAiOjE3MjkxMjMyMDAwMDB9
[INFO]
EXECUTOR! ~ constructor
[INFO]
https://www.floatplane.com/api/video/watchKey?token=eyJwYXRoIjoiL1ZpZGVvcy9za0pUTXNXbWdkLzM2MC5tcDQiLCJ0aW1lc3RhbXAiOjE3MjkxMjMyMDAwMDB9
[INFO]
Call [isVideoDetailsUrl("https://www.floatplane.com/post/l64I6dJA1X")] succesful [0ms] 

From /src/Wrapper.ts:143

export class FP_HLS_Executor {
    url: string;
    key: CryptoJS.lib.WordArray;

    constructor(url: string, key: CryptoJS.lib.WordArray) {
        this.url = url;
        this.key = key;
        log("EXECUTOR! ~ constructor");
    }

    findSegmentTime(index: number) {
        log("EXECUTOR! ~ findSegmentTime");
        throw new ScriptException("findSegmentTime not implemented");
    }

    cacheSegment(segment: object): void {
        log("EXECUTOR! ~ cacheSegment");
        throw new ScriptException("cacheSegment not implemented");
    }

    getCachedSegmentCount(): number {
        log("EXECUTOR! ~ getCachedSegmentCount");
        throw new ScriptException("getCachedSegmentCount not implemented");
    }

    getCachedSegment(index: number): string {
        log("EXECUTOR! ~ getCachedSegment");
        throw new ScriptException("getCachedSegment not implemented");
    }

    freeOldSegments(index: number | string) {
        log("EXECUTOR! ~ freeOldSegments");
        throw new ScriptException("freeOldSegments not implemented");
    }

    freeAllSegments(): void {
        log("EXECUTOR! ~ freeAllSegments");
        throw new ScriptException("freeAllSegments not implemented");
    }

    cleanup(): void {
        log("EXECUTOR! ~ cleanup");
        throw new ScriptException("cleanup not implemented");
    }

    executeRequest(url: string, headers: object, retryCount: number = 0): string {
        log("EXECUTOR! ~ executeRequest: " + url);
        if(url.includes("/chunk.m3u8?")) {
            const resp = http.GET(url, { ...FP_Headers, ...headers }, true);
            if(!resp.isOk) {
                if(retryCount < 5) {
                    return this.executeRequest(url, headers, retryCount + 1);
                }
                throw new ScriptException(`Failed to fetch HLS stream: ${resp.code}`);
            }
            return "";
        }

        throw new ScriptException("executeRequest not implemented: " + url);
    }
}

From /src/Grayjay.ts:121

import * as FP from "./Wrapper.ts";

function ToGrayjayVideoStream(...) {
    //...
    const source = new HLSSource({
        name: `#${video_index}${group_letter}=${variant.label} - ${title}`,
        url: origin + variant.url,
        duration: duration,
        priority: false
    });

    const enc_key = FP.getHlsToken(origin + variant.url);
    const executor = new FP.FP_HLS_Executor(origin + variant.url, enc_key)

    source.getRequestExecutor = () => executor;

    return source;
}
VoxelPrismatic commented 1 week ago

*empty string or resp.body in FP_HLS_Executor.executeRequest yields the same result