Добавлена синхронизация текущего времени воспроизведения видео с сервером и улучшена обработка потоковой передачи видео
This commit is contained in:
1 parent
ea08781fb6
commit
7b8c8b7f56
2 files changed
+73
-62
No files matched your search
@@ -5,44 +5,71 @@ const path = require('path');
|
|||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 3000;
|
const PORT = 3000;
|
||||||
|
|
||||||
// Путь к видеофайлу
|
// Укажите путь к фильму
|
||||||
const videoPath = path.join(__dirname, 'movie.mp4'); // Замените 'movie.mp4' на ваш фильм
|
const videoPath = path.join(__dirname, 'movie.mp4');
|
||||||
const serverStartTime = Date.now(); // Время запуска сервера
|
|
||||||
|
|
||||||
// Маршрут для главной страницы
|
// Определите битрейт (бит в секунду) фильма
|
||||||
app.use('/', express.static(path.join(__dirname, '')));
|
const videoBitrate = 1000000; // Например, 1 Мбит/с (проверьте, используя ffmpeg)
|
||||||
|
|
||||||
// Маршрут для видео
|
// Рассчитаем продолжительность фильма (в секундах)
|
||||||
|
const videoFileSize = fs.statSync(videoPath).size; // Размер файла в байтах
|
||||||
|
const videoDurationInSeconds = videoFileSize / (videoBitrate / 8); // Длительность = Размер файла / Байт/секунду
|
||||||
|
|
||||||
|
// Фиксируем момент запуска фильма
|
||||||
|
const serverStartTime = Date.now();
|
||||||
|
|
||||||
|
app.use(express.static(path.join(__dirname)));
|
||||||
|
|
||||||
|
// Маршрут для передачи видео
|
||||||
app.get('/video', (req, res) => {
|
app.get('/video', (req, res) => {
|
||||||
|
const stat = fs.statSync(videoPath);
|
||||||
|
const fileSize = stat.size;
|
||||||
|
|
||||||
|
// Рассчитываем текущую позицию фильма
|
||||||
|
const timeElapsedSinceStart = (Date.now() - serverStartTime) / 1000; // В секундах
|
||||||
|
const currentPlaybackTime = Math.min(timeElapsedSinceStart, videoDurationInSeconds);
|
||||||
|
|
||||||
|
// Рассчитываем начальную позицию в байтах
|
||||||
|
const startByte = Math.floor((currentPlaybackTime / videoDurationInSeconds) * fileSize);
|
||||||
|
|
||||||
const range = req.headers.range;
|
const range = req.headers.range;
|
||||||
if (!range) {
|
if (range) {
|
||||||
return res.status(400).send('Requires Range header');
|
const parts = range.replace(/bytes=/, '').split('-');
|
||||||
|
const start = parseInt(parts[0], 10);
|
||||||
|
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
|
||||||
|
|
||||||
|
const chunkSize = end - start + 1;
|
||||||
|
const file = fs.createReadStream(videoPath, { start, end });
|
||||||
|
const head = {
|
||||||
|
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
||||||
|
'Accept-Ranges': 'bytes',
|
||||||
|
'Content-Length': chunkSize,
|
||||||
|
'Content-Type': 'video/mp4',
|
||||||
|
};
|
||||||
|
|
||||||
|
res.writeHead(206, head);
|
||||||
|
file.pipe(res);
|
||||||
|
} else {
|
||||||
|
const file = fs.createReadStream(videoPath, { start: startByte });
|
||||||
|
const head = {
|
||||||
|
'Content-Length': fileSize - startByte,
|
||||||
|
'Content-Type': 'video/mp4',
|
||||||
|
};
|
||||||
|
|
||||||
|
res.writeHead(200, head);
|
||||||
|
file.pipe(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoSize = fs.statSync(videoPath).size;
|
|
||||||
const CHUNK_SIZE = 10 ** 6; // 1MB
|
|
||||||
const start = Number(range.replace(/\D/g, ''));
|
|
||||||
const end = Math.min(start + CHUNK_SIZE, videoSize - 1);
|
|
||||||
|
|
||||||
const videoStream = fs.createReadStream(videoPath, { start, end });
|
|
||||||
|
|
||||||
res.writeHead(206, {
|
|
||||||
'Content-Range': `bytes ${start}-${end}/${videoSize}`,
|
|
||||||
'Accept-Ranges': 'bytes',
|
|
||||||
'Content-Length': end - start + 1,
|
|
||||||
'Content-Type': 'video/mp4',
|
|
||||||
});
|
|
||||||
|
|
||||||
videoStream.pipe(res);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// API для получения текущего времени воспроизведения
|
// Маршрут для передачи текущего времени
|
||||||
app.get('/current-time', (req, res) => {
|
app.get('/time', (req, res) => {
|
||||||
const elapsedTime = (Date.now() - serverStartTime) / 1000; // Время с момента запуска сервера в секундах
|
const timeElapsedSinceStart = (Date.now() - serverStartTime) / 1000; // В секундах
|
||||||
res.json({ currentTime: elapsedTime });
|
const currentPlaybackTime = Math.min(timeElapsedSinceStart, videoDurationInSeconds);
|
||||||
|
|
||||||
|
res.json({ currentPlaybackTime });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Запуск сервера
|
app.listen(PORT, () => {
|
||||||
app.listen(PORT, '0.0.0.0', () => {
|
|
||||||
console.log(`Server is running on http://localhost:${PORT}`);
|
console.log(`Server is running on http://localhost:${PORT}`);
|
||||||
|
console.log(`Video duration is approximately ${Math.round(videoDurationInSeconds)} seconds.`);
|
||||||
});
|
});
|
||||||
+16
-32
@@ -1,4 +1,4 @@
|
|||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const video = document.getElementById('video');
|
const video = document.getElementById('video');
|
||||||
const playPauseBtn = document.getElementById('playPauseBtn');
|
const playPauseBtn = document.getElementById('playPauseBtn');
|
||||||
const muteBtn = document.getElementById('muteBtn');
|
const muteBtn = document.getElementById('muteBtn');
|
||||||
@@ -7,33 +7,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const volumeSlider = document.querySelector('.volume-slider');
|
const volumeSlider = document.querySelector('.volume-slider');
|
||||||
const videoContainer = document.querySelector('.video-frame');
|
const videoContainer = document.querySelector('.video-frame');
|
||||||
|
|
||||||
// Получение текущего времени воспроизведения с сервера
|
// Синхронизация времени
|
||||||
async function fetchCurrentTime() {
|
async function syncTime() {
|
||||||
try {
|
const response = await fetch('/time'); // Запрос текущего времени
|
||||||
const response = await fetch('/current-time');
|
const data = await response.json();
|
||||||
if (!response.ok) {
|
video.currentTime = data.currentPlaybackTime; // Устанавливаем время
|
||||||
throw new Error('Failed to fetch current time');
|
video.play(); // Автоматически начинаем воспроизведение
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
return data.currentTime; // Сервер возвращает время в секундах
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching current time:', error);
|
|
||||||
return 0; // Если ошибка, начать воспроизведение с начала
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Синхронизация времени видео с сервером
|
|
||||||
async function syncVideo() {
|
|
||||||
const currentTime = await fetchCurrentTime(); // Получаем время от сервера
|
|
||||||
video.currentTime = currentTime; // Устанавливаем текущее время видео
|
|
||||||
}
|
|
||||||
|
|
||||||
// Событие, когда метаданные видео загружены
|
|
||||||
video.addEventListener('loadedmetadata', async () => {
|
|
||||||
await syncVideo(); // Синхронизация видео с сервера
|
|
||||||
video.play(); // Запускаем видео
|
|
||||||
});
|
|
||||||
|
|
||||||
// Play/Pause
|
// Play/Pause
|
||||||
function togglePlay() {
|
function togglePlay() {
|
||||||
if (video.paused) {
|
if (video.paused) {
|
||||||
@@ -45,18 +26,18 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновление прогресса
|
// Update progress bar
|
||||||
function updateProgress() {
|
function updateProgress() {
|
||||||
const progress = (video.currentTime / video.duration) * 100;
|
const progress = (video.currentTime / video.duration) * 100;
|
||||||
progressBar.value = progress;
|
progressBar.value = progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Установка времени видео через прогресс-бар
|
// Set video time based on progress bar
|
||||||
function setVideoProgress() {
|
function setVideoProgress() {
|
||||||
video.currentTime = (progressBar.value * video.duration) / 100;
|
video.currentTime = (progressBar.value * video.duration) / 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Громкость
|
// Volume controls
|
||||||
function updateVolume() {
|
function updateVolume() {
|
||||||
video.volume = volumeSlider.value;
|
video.volume = volumeSlider.value;
|
||||||
updateVolumeIcon();
|
updateVolumeIcon();
|
||||||
@@ -89,7 +70,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Слушатели событий
|
// Event listeners
|
||||||
playPauseBtn.addEventListener('click', togglePlay);
|
playPauseBtn.addEventListener('click', togglePlay);
|
||||||
video.addEventListener('click', togglePlay);
|
video.addEventListener('click', togglePlay);
|
||||||
video.addEventListener('timeupdate', updateProgress);
|
video.addEventListener('timeupdate', updateProgress);
|
||||||
@@ -98,7 +79,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
volumeSlider.addEventListener('input', updateVolume);
|
volumeSlider.addEventListener('input', updateVolume);
|
||||||
fullscreenBtn.addEventListener('click', toggleFullscreen);
|
fullscreenBtn.addEventListener('click', toggleFullscreen);
|
||||||
|
|
||||||
// Поддержка клавиш на клавиатуре
|
// Синхронизация времени при загрузке страницы
|
||||||
|
syncTime();
|
||||||
|
|
||||||
|
// Keyboard controls
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.code === 'Space') {
|
if (e.code === 'Space') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -106,5 +90,5 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Video player initialized and synced with the server.');
|
console.log('Video player initialized');
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user