File size: 3,398 Bytes
4d3248c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

<script>
(function () {
    'use strict';
    var ctx = null;
    var nextTime = 0;
    var segNum = 0;
    var lastSig = '';

    function log(msg) {
        console.log('[StreamPlayer]', msg);
        var el = document.getElementById('dbg-log');
        if (el) {
            el.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg + '
' + el.textContent;
        }
    }

    function getCtx() {
        if (!ctx || ctx.state === 'closed') {
            ctx = new AudioContext({ sampleRate: 22050 });
            nextTime = 0;
            segNum = 0;
            log('AudioContext created, state=' + ctx.state);
        }
        if (ctx.state === 'suspended') {
            ctx.resume().then(function() { log('AudioContext resumed'); }).catch(function(e) { log('resume error: ' + e); });
        }
        return ctx;
    }

    function playChunk(rawPath) {
        segNum++;
        var seg = segNum;
        var url = '/gradio_api/file=' + rawPath.replace(/\\/g, '/').replace(/\/g, '/');
        log('seg ' + seg + ' | fetching: ' + url);

        var c = getCtx();
        if (c.state === 'suspended') c.resume().catch(function(){});

        fetch(url)
            .then(function(r) {
                log('seg ' + seg + ' | HTTP ' + r.status);
                if (!r.ok) throw new Error('HTTP ' + r.status + ' for ' + url);
                return r.arrayBuffer();
            })
            .then(function(buf) {
                if (buf.byteLength === 0) throw new Error('Received 0 bytes');
                log('seg ' + seg + ' | arrayBuffer ' + buf.byteLength + ' bytes, decoding...');
                return getCtx().decodeAudioData(buf);
            })
            .then(function(decoded) {
                var ac = getCtx();
                var src = ac.createBufferSource();
                src.buffer = decoded;
                src.connect(ac.destination);
                var now = ac.currentTime;
                var at = (nextTime > now + 0.05) ? nextTime : (now + 0.05);
                src.start(at);
                nextTime = at + decoded.duration;
                log('seg ' + seg + ' | ' + decoded.duration.toFixed(2) + 's | start@' + at.toFixed(3) + ' end@' + nextTime.toFixed(3));
            })
            .catch(function(e) {
                log('ERROR seg ' + seg + ': ' + e.message);
                console.error('[StreamPlayer]', e);
            });
    }

    function checkSignal() {
        var sig = document.getElementById('sph-sig');
        if (!sig) return;
        var val = sig.getAttribute('data-v') || '';
        if (val === lastSig) return;
        lastSig = val;
        log('signal received: ' + val);
        if (!val || val === '__idle__') return;
        if (val === '__reset__') {
            if (ctx && ctx.state !== 'closed') ctx.close().catch(function(){});
            ctx = null; nextTime = 0; segNum = 0;
            log('player reset');
        } else if (val === '__done__') {
            log('all chunks sent!');
        } else {
            playChunk(val);
        }
    }

    document.addEventListener('click', function() {
        if (ctx && ctx.state === 'suspended') ctx.resume().catch(function(){});
    }, true);

    setInterval(checkSignal, 50);
    log('StreamPlayer JS loaded, polling every 50ms');
})();
</script>