Spaces:
Sleeping
Sleeping
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Multilanguage Player</title> | |
<script src="https://cdn.dashjs.org/latest/dash.all.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/webvtt-parser@2.1.2/dist/parser.min.js"></script> | |
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
<style> | |
body { | |
font-family: Arial, sans-serif; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
margin: 0; | |
background-color: #ffffff; | |
} | |
.top-div { | |
width: 100%; | |
height: 100px; | |
background-color: #3C8DF9; | |
margin-bottom: 20px; | |
} | |
.container { | |
display: flex; | |
justify-content: space-between; | |
width: 90%; | |
max-width: 1200px; | |
} | |
.left-panel { | |
width: 30%; | |
padding: 20px; | |
box-sizing: border-box; | |
} | |
.right-panel { | |
width: 65%; | |
padding: 20px; | |
box-sizing: border-box; | |
} | |
.frame { | |
width: 402px; | |
height: 720px; | |
border: 2px solid #ccc; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
margin-bottom: 10px; | |
} | |
video { | |
max-width: 100%; | |
max-height: 100%; | |
box-shadow: 0 0 10px rgba(0,0,0,0.1); | |
} | |
select, input, button { | |
margin: 10px 0; | |
padding: 5px; | |
width: 100%; | |
} | |
#result { | |
margin-top: 10px; | |
font-weight: bold; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="top-div"><img src="{{ url_for('static', filename='images/logo.png') }}" alt="Bytedance" height="100px"></div> | |
<div class="container"> | |
<div class="left-panel"> | |
<h2>FLV Stream Url</h2> | |
<form id="flvForm"> | |
<input type="text" id="flvInput" placeholder="http://example.com/stream.flv" required> | |
<button type="submit">Play Stream</button> | |
</form> | |
<div id="result"></div> | |
<form id="terminateStreamForm"> | |
<button type="submit">Stop Stream</button> | |
</form> | |
<h3>Language Selection</h3> | |
<select id="captionSelect"> | |
<option value="original">Original</option> | |
<option value="es">Spanish</option> | |
<option value="ru">Russian</option> | |
<option value="en">English</option> | |
<option value="zh">Chinese</option> | |
</select> | |
<h3>Model Selection</h3> | |
<select id="models"> | |
<option value="base">Base</option> | |
<option value="small">Small</option> | |
<option value="medium">Medium</option> | |
<option value="large">Large</option> | |
<option value="large-v2">Large-V2</option> | |
</select> | |
</div> | |
<div class="right-panel"> | |
<div class="frame"> | |
<div id="waitingMessage">Waiting for the stream...</div> | |
<video id="videoPlayer" controls style="display: none;"></video> | |
</div> | |
</div> | |
</div> | |
<script> | |
(function () { | |
var url = "{{ url_for('serve_file', filename='manifest.mpd') }}"; | |
var player = dashjs.MediaPlayer().create(); | |
var video = document.querySelector("#videoPlayer"); | |
var waitingMessage = document.querySelector("#waitingMessage"); | |
var captionSelect = document.querySelector("#captionSelect"); | |
var currentLanguage = "original"; | |
var refreshInterval = 10000; | |
var desiredDelay = 45; | |
var checkInterval = 5000; // Check every 5 seconds | |
function initializePlayer() { | |
// Reset the player if it's already initialized | |
if (player.isReady()) { | |
player.reset(); | |
} | |
console.log("Initializing the player with %s", url) | |
player.updateSettings({ | |
streaming: { | |
delay: { | |
liveDelay: 50, | |
}, | |
buffer: { | |
bufferToKeep: 40, | |
bufferTimeAtTopQuality: 50, | |
bufferTimeAtTopQualityLongForm: 50, | |
initialBufferLevel: 50, | |
}, | |
} | |
}); | |
fetch(url, {method: 'GET', cache: "no-store"}) | |
.then(response => { | |
if (response.ok) { | |
console.log("Response: ", response.text()) | |
} else { | |
console.log("Response not ok: ", response.body) | |
} | |
}) | |
.catch(() => { | |
console.log("Error") | |
}); | |
player.initialize(video, url, false); | |
// player.attachView(video); | |
player.setMute(true) | |
player.enableForcedTextStreaming(true); | |
player.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, onStreamInitialized); | |
player.on(dashjs.MediaPlayer.events.ERROR, onPlayerError); | |
} | |
function checkStreamAvailability() { | |
fetch(url, {method: 'GET', cache: "no-store"}) | |
.then(response => { | |
if (response.ok) { | |
console.log("Stream is ready") | |
//manifest is there, wait a sec for init segments | |
setTimeout(initializePlayer, 15000) | |
} else { | |
setTimeout(checkStreamAvailability, checkInterval); | |
} | |
}) | |
.catch(() => { | |
setTimeout(checkStreamAvailability, checkInterval); | |
}); | |
} | |
function onStreamInitialized() { | |
console.log("Stream initialized, setting up captions"); | |
setupCaptions(); | |
setInterval(refreshCaptions, refreshInterval); | |
waitForInitialData(); | |
player.play() | |
} | |
function onPlayerError(e) { | |
console.log("Player error:", e); | |
let errorCode = e.code || e.error?.code || e.error?.error?.code; | |
console.log("Extracted error code:", errorCode); | |
if (errorCode === 25) { | |
console.log("Rescheduling...") | |
waitingMessage.style.display = "block"; | |
video.style.display = "none"; | |
checkStreamAvailability(); | |
} | |
console.log("None...") | |
//waitingMessage.style.display = "block"; | |
//video.style.display = "none"; | |
//checkStreamAvailability(); | |
} | |
function waitForInitialData() { | |
console.log("Waiting for initial data"); | |
if (player.getBufferLength() > 0 && video.readyState >= 2) { | |
console.log("Initial data buffered, starting playback"); | |
waitingMessage.style.display = "none"; | |
video.style.display = "block"; | |
//player.play(); | |
} else { | |
setTimeout(waitForInitialData, 100); | |
} | |
} | |
function parseVTT(vttContent) { | |
const lines = vttContent.trim().split('\n'); | |
let cues = []; | |
let cue = {}; | |
for (let i = 0; i < lines.length; i++) { | |
if (lines[i].includes('-->')) { | |
const [start, end] = lines[i].split('-->').map(timeString => { | |
const [hours, minutes, seconds] = timeString.trim().split(':'); | |
return parseFloat(hours) * 3600 + parseFloat(minutes) * 60 + parseFloat(seconds); | |
}); | |
cue = {start, end, text: ''}; | |
} else if (lines[i].trim() !== '' && cue.start !== undefined) { | |
cue.text += lines[i] + '\n'; | |
} else if (lines[i].trim() === '' && cue.text) { | |
cues.push(cue); | |
cue = {}; | |
} | |
} | |
if (cue.text) { | |
cues.push(cue); | |
} | |
return cues; | |
} | |
function loadCaptions(lang) { | |
console.log("Loading captions for language: " + lang); | |
var baseUrl = "{{ url_for('serve_file', filename='') }}"; // This will give the base URL for the 'serve_file' endpoint. | |
var fileName = "captions_" + lang + ".vtt"; | |
var captionUrl = baseUrl + fileName; | |
for (var i = 0; i < video.textTracks.length; i++) { | |
video.textTracks[i].mode = 'disabled'; | |
} | |
var track = Array.from(video.textTracks).find(t => t.language === lang); | |
if (!track) { | |
track = video.addTextTrack("captions", lang, lang); | |
} | |
track.mode = 'showing'; | |
updateTrackCues(track, captionUrl); | |
currentLanguage = lang; | |
console.log("Captions loaded for language: " + lang); | |
} | |
function updateTrackCues(track, url) { | |
fetch(url) | |
.then(response => response.text()) | |
.then(vttContent => { | |
const cues = parseVTT(vttContent); | |
while (track.cues.length > 0) { | |
track.removeCue(track.cues[0]); | |
} | |
cues.forEach(cue => { | |
const vttCue = new VTTCue(cue.start, cue.end, cue.text.trim()); | |
track.addCue(vttCue); | |
}); | |
}) | |
.catch(error => console.error('Error updating captions:', error)); | |
} | |
function refreshCaptions() { | |
if (currentLanguage) { | |
var track = Array.from(video.textTracks).find(t => t.language === currentLanguage); | |
if (track) { | |
var baseUrl = "{{ url_for('serve_file', filename='') }}"; // This will give the base URL for the 'serve_file' endpoint. | |
var fileName = "captions_" + currentLanguage + ".vtt"; | |
var captionUrl = baseUrl + fileName; | |
updateTrackCues(track, captionUrl); | |
} | |
} | |
} | |
function setupCaptions() { | |
var tracks = player.getTracksFor('text'); | |
console.log("Available text tracks:", tracks); | |
if (tracks.length > 0) { | |
captionSelect.innerHTML = ''; | |
tracks.forEach(function (track) { | |
var option = document.createElement('option'); | |
option.value = track.lang; | |
option.text = track.lang; | |
captionSelect.appendChild(option); | |
}); | |
loadCaptions(tracks[0].lang); | |
} else { | |
loadCaptions(currentLanguage); | |
} | |
} | |
captionSelect.addEventListener("change", function () { | |
loadCaptions(this.value); | |
}); | |
// FLV Stream Checker | |
$('#flvForm').submit(function(e) { | |
$('#waitingMessage').text("Checking the url..."); | |
e.preventDefault(); | |
$.ajax({ | |
url: '/terminate', | |
method: 'POST', | |
data: null, | |
}); | |
if (player.isReady()) { | |
player.pause() | |
} | |
$.ajax({ | |
url: '/check_flv', | |
method: 'POST', | |
data: { url: $('#flvInput').val(), model: $('#models').val() }, | |
success: function(response) { | |
$('#waitingMessage').text(response.message); | |
if (response.status === 'success') { | |
waitingMessage.style.display = "block"; | |
video.style.display = "none"; | |
//initializePlayer(); | |
//checkStreamAvailability(); | |
} | |
}, | |
error: function() { | |
$('#result').text('An error occurred'); | |
} | |
}); | |
}); | |
// FLV Stream Checker | |
$('#terminateStreamForm').submit(function(e) { | |
e.preventDefault(); | |
$.ajax({ | |
url: '/terminate', | |
method: 'POST', | |
data: null, | |
}); | |
if (player.isReady()) { | |
player.pause() | |
} | |
}); | |
// Start checking for stream availability | |
//initializePlayer(); | |
checkStreamAvailability(); | |
// Log current live delay every 5 seconds | |
// setInterval(() => { | |
// var currentLiveDelay = player.duration() - player.time(); | |
// console.log("Current live delay:", currentLiveDelay); | |
//}, 5000); | |
})(); | |
</script> | |
</body> | |
</html> |