import React, { useEffect, useRef } from 'react';
import Hls from 'hls.js';
import './Player.css';
// Class để quản lý subtitle SRT
class SubtitleManager {
constructor(videoElement, subtitleDisplay) {
this.video = videoElement;
this.display = subtitleDisplay;
this.subtitles = [];
this.currentSubIndex = -1;
this.isEnabled = false;
}
async loadSRT(url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('Không tải được file SRT');
const text = await response.text();
this.subtitles = this.parseSRT(text);
this.updateSubtitles();
return true;
} catch (error) {
console.error(error.message);
return false;
}
}
parseSRT(srtText) {
const cues = [];
const blocks = srtText.trim().split(/\n\s*\n/);
for (const block of blocks) {
const lines = block.split('\n').map(line => line.trim());
if (lines.length < 2 || !lines[1].includes(' --> ')) continue;
const timeLine = lines[1];
const [startStr, endStr] = timeLine.split(' --> ');
const text = lines.slice(2).join('\n');
const start = this.timeToSeconds(startStr);
const end = this.timeToSeconds(endStr);
if (!isNaN(start) && !isNaN(end) && start < end) {
cues.push({ start, end, text });
}
}
return cues.sort((a, b) => a.start - b.start);
}
timeToSeconds(timeStr) {
try {
const [hours, minutes, seconds] = timeStr.replace(',', '.').split(':');
return (+hours * 3600) + (+minutes * 60) + (+seconds);
} catch (e) {
return NaN;
}
}
updateSubtitles() {
if (!this.isEnabled) {
this.display.innerHTML = '';
return;
}
const currentTime = this.video.currentTime;
let subtitleShown = false;
for (let i = 0; i < this.subtitles.length; i++) {
const cue = this.subtitles[i];
if (currentTime >= cue.start && currentTime < cue.end) {
this.display.innerHTML = '';
const subElement = document.createElement('span');
subElement.className = 'subtitle-text';
subElement.textContent = cue.text;
this.display.appendChild(subElement);
this.currentSubIndex = i;
subtitleShown = true;
break;
}
}
if (!subtitleShown) {
this.display.innerHTML = '';
this.currentSubIndex = -1;
}
}
toggleSubtitles() {
this.isEnabled = !this.isEnabled;
this.updateSubtitles();
}
initialize() {
this.video.addEventListener('timeupdate', () => this.updateSubtitles());
this.video.addEventListener('seeked', () => this.updateSubtitles());
this.video.addEventListener('play', () => this.updateSubtitles());
this.video.addEventListener('pause', () => this.updateSubtitles());
}
}
const Playerconcak = () => {
const playPauseBtnRef = useRef(null);
const theaterBtnRef = useRef(null);
const fullScreenBtnRef = useRef(null);
const miniPlayerBtnRef = useRef(null);
const muteBtnRef = useRef(null);
const captionsBtnRef = useRef(null);
const speedBtnRef = useRef(null);
const currentTimeElemRef = useRef(null);
const totalTimeElemRef = useRef(null);
const previewImgRef = useRef(null);
const thumbnailImgRef = useRef(null);
const volumeSliderRef = useRef(null);
const videoContainerRef = useRef(null);
const timelineContainerRef = useRef(null);
const videoRef = useRef(null);
const videoControlsContainerRef = useRef(null);
const videoListRef = useRef(null);
const titleeRef = useRef(null);
const subtitleDisplayRef = useRef(null);
let isScrubbing = false;
let wasPaused;
let timeout;
// Hàm cập nhật kích thước font subtitle
const updateFontSize = () => {
const videoContainer = videoContainerRef.current;
if (!videoContainer) return;
const width = window.innerWidth;
let fontSize = '24px'; // Default font size
const breakpoints = {
359: '14px',
360: '14px',
375: '14px',
412: '15px',
480: '16px',
640: '17px',
768: '18px',
800: '18px',
1024: '28px',
1280: '30px',
1366: '35px',
1440: '55px',
1920: '65px',
};
for (let breakpoint in breakpoints) {
if (width <= parseInt(breakpoint)) {
fontSize = breakpoints[breakpoint];
break;
}
}
// Cập nhật CSS variable trên video-container
videoContainer.style.setProperty('--subtitle-font-size', fontSize);
};
useEffect(() => {
const playPauseBtn = playPauseBtnRef.current;
const theaterBtn = theaterBtnRef.current;
const fullScreenBtn = fullScreenBtnRef.current;
const miniPlayerBtn = miniPlayerBtnRef.current;
const muteBtn = muteBtnRef.current;
const captionsBtn = captionsBtnRef.current;
const speedBtn = speedBtnRef.current;
const currentTimeElem = currentTimeElemRef.current;
const totalTimeElem = totalTimeElemRef.current;
const previewImg = previewImgRef.current;
const thumbnailImg = thumbnailImgRef.current;
const volumeSlider = volumeSliderRef.current;
const videoContainer = videoContainerRef.current;
const timelineContainer = timelineContainerRef.current;
const video = videoRef.current;
const videoControlsContainer = videoControlsContainerRef.current;
const videoList = videoListRef.current;
const titlee = titleeRef.current;
const subtitleDisplay = subtitleDisplayRef.current;
let hls = null;
if (Hls.isSupported()) {
hls = new Hls();
hls.loadSource(video.src);
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = video.src;
}
const subtitleManager = new SubtitleManager(video, subtitleDisplay);
subtitleManager.loadSRT(
'https://raw.githubusercontent.com/Huyenuiio/Data-src/main/video/yoru%20no%20kurage/tap1/%5BSubtitleTools.com%5D%20%5BErai-raws%5D%20Yoru%20no%20Kurage%20wa%20Oyogenai%20-%2001%20%5B480p%5D%5B26196FA3%5D.srt'
).then(() => subtitleManager.initialize());
// Gọi updateFontSize lần đầu
updateFontSize();
// Thêm listener cho resize
window.addEventListener('resize', updateFontSize);
const handleKeydown = (e) => {
const tagName = document.activeElement.tagName.toLowerCase();
if (tagName === 'input') return;
switch (e.key.toLowerCase()) {
case ' ':
if (tagName === 'button') return;
case 'k':
togglePlay();
break;
case 'f':
toggleFullScreenMode();
break;
case 't':
toggleTheaterMode();
break;
case 'i':
toggleMiniPlayerMode();
break;
case 'm':
toggleMute();
break;
case 'arrowleft':
case 'j':
skip(-5);
break;
case 'arrowright':
case 'l':
skip(5);
break;
case 'c':
toggleCaptions();
break;
default:
break;
}
};
const handleTimelineUpdate = (e) => {
const rect = timelineContainer.getBoundingClientRect();
const percent = Math.min(Math.max(0, e.x - rect.x), rect.width) / rect.width;
const previewImgNumber = Math.max(1, Math.floor((percent * video.duration) / 10));
const previewImgSrc = `assets/previewImgs/preview${previewImgNumber}.jpg`;
if (previewImg) {
previewImg.src = previewImgSrc;
}
timelineContainer.style.setProperty('--preview-position', percent);
if (isScrubbing && thumbnailImg) {
e.preventDefault();
thumbnailImg.src = previewImgSrc;
timelineContainer.style.setProperty('--progress-position', percent);
}
};
const toggleScrubbing = (e) => {
const rect = timelineContainer.getBoundingClientRect();
const percent = Math.min(Math.max(0, e.x - rect.x), rect.width) / rect.width;
isScrubbing = (e.buttons & 1) === 1;
videoContainer.classList.toggle('scrubbing', isScrubbing);
if (isScrubbing) {
wasPaused = video.paused;
video.pause();
} else {
video.currentTime = percent * video.duration;
if (!wasPaused) video.play();
}
handleTimelineUpdate(e);
};
const changePlaybackSpeed = () => {
let newPlaybackRate = video.playbackRate + 0.25;
if (newPlaybackRate > 2) newPlaybackRate = 0.25;
video.playbackRate = newPlaybackRate;
speedBtn.textContent = `${newPlaybackRate}x`;
};
const toggleCaptions = () => {
subtitleManager.toggleSubtitles();
videoContainer.classList.toggle('captions', subtitleManager.isEnabled);
};
const formatDuration = (time) => {
const seconds = Math.floor(time % 60);
const minutes = Math.floor(time / 60) % 60;
const hours = Math.floor(time / 3600);
const leadingZeroFormatter = new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 });
return hours === 0
? `${minutes}:${leadingZeroFormatter.format(seconds)}`
: `${hours}:${leadingZeroFormatter.format(minutes)}:${leadingZeroFormatter.format(seconds)}`;
};
const skip = (duration) => {
video.currentTime += duration;
};
const toggleMute = () => {
video.muted = !video.muted;
};
const handleVolumeChange = () => {
volumeSlider.value = video.volume;
let volumeLevel;
if (video.muted || video.volume === 0) {
volumeSlider.value = 0;
volumeLevel = 'muted';
} else if (video.volume >= 0.5) {
volumeLevel = 'high';
} else {
volumeLevel = 'low';
}
videoContainer.dataset.volumeLevel = volumeLevel;
};
const toggleTheaterMode = () => {
videoContainer.classList.toggle('theater');
};
const toggleFullScreenMode = () => {
if (!document.fullscreenElement) {
videoContainer.requestFullscreen();
} else {
document.exitFullscreen();
}
};
const toggleMiniPlayerMode = () => {
if (videoContainer.classList.contains('mini-player')) {
document.exitPictureInPicture();
} else {
video.requestPictureInPicture();
}
};
const togglePlay = () => {
video.paused ? video.play() : video.pause();
};
const showControls = () => {
videoControlsContainer.style.opacity = '1';
clearTimeout(timeout);
};
const hideControls = () => {
timeout = setTimeout(() => {
videoControlsContainer.style.opacity = '0';
if (subtitleManager.isEnabled) {
subtitleDisplay.style.opacity = '1';
}
}, 2000);
};
const handleVideoSelect = (e) => {
const vidElement = e.currentTarget;
const videoElement = vidElement.querySelector('video');
const newSrc = videoElement.getAttribute('src');
const newTitle = vidElement.querySelector('.title').textContent;
const newSubtitle = videoElement.getAttribute('data-subtitle');
if (Hls.isSupported() && hls) {
hls.destroy();
hls = new Hls();
hls.loadSource(newSrc);
hls.attachMedia(video);
video.play();
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = newSrc;
video.load();
video.play();
}
const allVidElements = videoList.querySelectorAll('.vid');
allVidElements.forEach((vid) => vid.classList.remove('active'));
vidElement.classList.add('active');
if (titlee) {
titlee.textContent = newTitle;
}
if (newSubtitle) {
subtitleManager.loadSRT(newSubtitle);
} else {
subtitleManager.subtitles = [];
subtitleManager.updateSubtitles();
}
};
document.addEventListener('keydown', handleKeydown);
timelineContainer.addEventListener('mousemove', handleTimelineUpdate);
timelineContainer.addEventListener('mousedown', toggleScrubbing);
document.addEventListener('mouseup', (e) => {
if (isScrubbing) toggleScrubbing(e);
});
document.addEventListener('mousemove', (e) => {
if (isScrubbing) handleTimelineUpdate(e);
});
speedBtn.addEventListener('click', changePlaybackSpeed);
captionsBtn.addEventListener('click', toggleCaptions);
video.addEventListener('loadeddata', () => {
totalTimeElem.textContent = formatDuration(video.duration);
});
video.addEventListener('timeupdate', () => {
currentTimeElem.textContent = formatDuration(video.currentTime);
const percent = video.currentTime / video.duration;
timelineContainer.style.setProperty('--progress-position', percent);
});
muteBtn.addEventListener('click', toggleMute);
volumeSlider.addEventListener('input', (e) => {
video.volume = e.target.value;
video.muted = e.target.value === 0;
});
video.addEventListener('volumechange', handleVolumeChange);
theaterBtn.addEventListener('click', toggleTheaterMode);
fullScreenBtn.addEventListener('click', toggleFullScreenMode);
miniPlayerBtn.addEventListener('click', toggleMiniPlayerMode);
document.addEventListener('fullscreenchange', () => {
videoContainer.classList.toggle('full-screen', !!document.fullscreenElement);
});
video.addEventListener('enterpictureinpicture', () => {
videoContainer.classList.add('mini-player');
});
video.addEventListener('leavepictureinpicture', () => {
videoContainer.classList.remove('mini-player');
});
playPauseBtn.addEventListener('click', togglePlay);
video.addEventListener('click', togglePlay);
video.addEventListener('play', () => {
videoContainer.classList.remove('paused');
});
video.addEventListener('pause', () => {
videoContainer.classList.add('paused');
});
document.addEventListener('mousemove', showControls);
document.addEventListener('keydown', showControls);
videoControlsContainer.addEventListener('mouseover', showControls);
document.addEventListener('mouseout', hideControls);
document.addEventListener('keyup', hideControls);
videoControlsContainer.addEventListener('mouseleave', hideControls);
const vidElements = videoList.querySelectorAll('.vid');
vidElements.forEach((vid) => {
vid.addEventListener('click', handleVideoSelect);
});
return () => {
document.removeEventListener('keydown', handleKeydown);
timelineContainer.removeEventListener('mousemove', handleTimelineUpdate);
timelineContainer.removeEventListener('mousedown', toggleScrubbing);
document.removeEventListener('mouseup', toggleScrubbing);
document.removeEventListener('mousemove', handleTimelineUpdate);
speedBtn.removeEventListener('click', changePlaybackSpeed);
captionsBtn.removeEventListener('click', toggleCaptions);
video.removeEventListener('loadeddata', () => {});
video.removeEventListener('timeupdate', () => {});
muteBtn.removeEventListener('click', toggleMute);
volumeSlider.removeEventListener('input', () => {});
video.removeEventListener('volumechange', handleVolumeChange);
theaterBtn.removeEventListener('click', toggleTheaterMode);
fullScreenBtn.removeEventListener('click', toggleFullScreenMode);
miniPlayerBtn.removeEventListener('click', toggleMiniPlayerMode);
document.removeEventListener('fullscreenchange', () => {});
video.removeEventListener('enterpictureinpicture', () => {});
video.removeEventListener('leavepictureinpicture', () => {});
playPauseBtn.removeEventListener('click', togglePlay);
video.removeEventListener('click', togglePlay);
video.removeEventListener('play', () => {});
video.removeEventListener('pause', () => {});
document.removeEventListener('mousemove', showControls);
document.removeEventListener('keydown', showControls);
videoControlsContainer.removeEventListener('mouseover', showControls);
document.removeEventListener('mouseout', hideControls);
document.removeEventListener('keyup', hideControls);
videoControlsContainer.removeEventListener('mouseleave', hideControls);
vidElements.forEach((vid) => {
vid.removeEventListener('click', handleVideoSelect);
});
window.removeEventListener('resize', updateFontSize); // Cleanup resize listener
if (hls) {
hls.destroy();
}
};
}, []);
return (
{/* Các video khác giữ nguyên và thêm data-subtitle nếu cần */}
);
};
export default Playerconcak;
0:00
/
Tập 1 - "Ta yêu em" và Tự động thủ kí nhân hình
Tập 2 - "Sẽ không quay về nữa"
Tập 1 - "Ta yêu em" và Tự động thủ kí nhân hình