File size: 5,818 Bytes
5075889
 
3646f0a
5075889
 
 
3646f0a
7716887
b02f5a3
3646f0a
f604e32
587ad0a
2bcf446
120b59b
2bcf446
 
18c180f
587ad0a
2bcf446
 
 
 
 
 
587ad0a
2bcf446
 
 
 
 
 
 
3646f0a
 
 
2bcf446
 
 
587ad0a
 
 
f604e32
5075889
2af4f1e
7716887
 
a432a06
 
02f2f15
a432a06
1b8d391
f604e32
1b8d391
 
 
 
 
 
 
5075889
f604e32
1b8d391
3646f0a
 
f604e32
02f2f15
5075889
120b59b
5075889
7716887
1588f0a
9e303de
 
 
 
 
 
 
 
120b59b
 
9e303de
 
120b59b
9e303de
 
 
 
 
120b59b
9e303de
d09403a
120b59b
1b8d391
 
 
120b59b
 
 
3f8c131
b02f5a3
 
 
 
 
120b59b
 
 
f604e32
 
120b59b
 
f604e32
 
120b59b
 
3646f0a
120b59b
 
 
a432a06
b02f5a3
 
 
 
 
 
 
 
 
 
 
 
 
f604e32
b02f5a3
 
 
 
 
 
9e303de
b02f5a3
5075889
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3f8c131
5075889
 
 
 
 
 
587ad0a
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
const videoPlayer = document.getElementById("tvPlayer");

// Ensure iOS allows inline playback
videoPlayer.setAttribute("webkit-playsinline", "true");
videoPlayer.setAttribute("playsinline", "true");

const LAST_CHANNEL_KEY = "last_selected_channel";
let currentChannel = null;
let smoothPlayback = true;

// Fetch channels and populate buttons
async function fetchChannels() {
    try {
        const response = await fetch("/channels");
        const data = await response.json();
        const channelButtons = document.querySelector(".channel-buttons");
        channelButtons.innerHTML = "";

        if (!data.channels || data.channels.length === 0) {
            channelButtons.innerHTML = "<p>No channels available</p>";
            document.getElementById("channelName").textContent = "No channels available";
            document.getElementById("currentShow").textContent = "No active streams.";
            return;
        }

        data.channels.forEach(channel => {
            const button = document.createElement("button");
            button.textContent = `Channel ${channel}`;
            button.onclick = () => changeChannel(channel);
            channelButtons.appendChild(button);
        });

        const lastChannel = localStorage.getItem(LAST_CHANNEL_KEY);
        const initialChannel = data.channels.includes(lastChannel) ? lastChannel : data.channels[0];
        changeChannel(initialChannel);
    } catch (error) {
        console.error("Failed to load channels:", error);
        document.querySelector(".channel-buttons").innerHTML = "<p>Error loading channels</p>";
    }
}

// Change channel and seek to correct timestamp
async function changeChannel(channel) {
    if (currentChannel === channel) return;
    currentChannel = channel;

    document.getElementById("channelName").textContent = `Channel ${channel}`;
    document.getElementById("currentShow").textContent = `Now Playing: Loading...`;

    const streamUrl = `/hls/${channel}/index.m3u8`;
    let seekTime = 0;

    try {
        const response = await fetch(`/timestamp?channel=${channel}`);
        const data = await response.json();
        if (data.elapsed) seekTime = data.elapsed;
    } catch (error) {
        console.error("Failed to fetch timestamp:", error);
    }

    // Ensure seekTime is valid
    if (seekTime < 0 || isNaN(seekTime)) seekTime = 0;
    localStorage.setItem(LAST_CHANNEL_KEY, channel);

    // Stop previous HLS instance if it exists
    if (Hls.isSupported()) {
        if (videoPlayer.hlsInstance) {
            videoPlayer.hlsInstance.destroy();
        }

        const hls = new Hls({
    maxBufferLength: 60,  // Reduce buffer length to avoid drastic jumps
    maxMaxBufferLength: 120,
    maxBufferSize: 200 * 1000 * 1000, // Reduce max buffer size
    liveSyncDurationCount: 3, // Reduce to improve sync
    enableWorker: true,
    lowLatencyMode: true,
    backBufferLength: 30 // Keep back buffer smaller
});

        hls.loadSource(streamUrl);
hls.attachMedia(videoPlayer);
videoPlayer.hlsInstance = hls;

hls.on(Hls.Events.MANIFEST_PARSED, () => {
    console.log(`Seeking to ${seekTime}s`);
    videoPlayer.currentTime = seekTime; // Seek only once here
    videoPlayer.play().catch(err => console.error("Playback error:", err));
});

// Remove BUFFER_APPENDED seeking to prevent repeated jumpsp

        hls.on(Hls.Events.ERROR, (event, data) => {
            if (data.fatal) {
                console.error("HLS Fatal Error:", data);
            }
        });

        videoPlayer.addEventListener("waiting", () => {
            console.warn("Video buffering...");
            smoothPlayback = false;
        });

        videoPlayer.addEventListener("playing", () => {
            smoothPlayback = true;
        });

        document.getElementById("currentShow").textContent = `Now Playing: Channel ${channel}`;
    } 
    else if (videoPlayer.canPlayType("application/vnd.apple.mpegurl")) {
        videoPlayer.src = streamUrl;
        videoPlayer.addEventListener("loadedmetadata", () => {
            console.log(`Seeking to ${seekTime}s`);
            videoPlayer.currentTime = seekTime;
            videoPlayer.play().catch(err => console.error("Playback error:", err));
        });

        document.getElementById("currentShow").textContent = `Now Playing: Channel ${channel}`;
    }
}

// Periodically check for playback drift
setInterval(async () => {
    if (!currentChannel || !smoothPlayback) return;

    try {
        const response = await fetch(`/timestamp?channel=${currentChannel}`);
        const data = await response.json();
        if (!data.elapsed) return;

        const serverTime = data.elapsed;
        const playerTime = videoPlayer.currentTime;
        const diff = Math.abs(serverTime - playerTime);

        if (diff > 3) {
            console.log(`Auto-correcting drift: ${playerTime}${serverTime}`);
            videoPlayer.currentTime = serverTime;
        }
    } catch (error) {
        console.error("Failed to sync playback:", error);
    }
}, 363636366633626); // More frequent drift correction

function togglePlay() {
    if (videoPlayer.paused) {
        videoPlayer.play();
    } else {
        videoPlayer.pause();
    }
}

function toggleFullscreen() {
    if (document.fullscreenElement) {
        document.exitFullscreen();
    } else if (videoPlayer.requestFullscreen) {
        videoPlayer.requestFullscreen();
    } else if (videoPlayer.webkitRequestFullscreen) {
        videoPlayer.webkitRequestFullscreen();
    } else if (videoPlayer.msRequestFullscreen) {
        videoPlayer.msRequestFullscreen();
    } else if (videoPlayer.webkitEnterFullscreen) {
        videoPlayer.webkitEnterFullscreen();
    } else {
        alert("Fullscreen is not supported on this device.");
    }
}

window.onload = fetchChannels;