268 lines
7.0 KiB
Vue
268 lines
7.0 KiB
Vue
<template>
|
|
<div class="video-player-container px-2 sm:px-0">
|
|
<div class="relative">
|
|
<!-- Video.js Player -->
|
|
<div data-vjs-player>
|
|
<video
|
|
ref="videoPlayer"
|
|
class="video-js vjs-default-skin vjs-big-play-centered w-full rounded-lg"
|
|
controls
|
|
preload="auto"
|
|
:poster="posterUrl"
|
|
playsinline
|
|
>
|
|
<source :src="videoUrl" />
|
|
<p class="vjs-no-js">
|
|
Pour voir cette vidéo, activez JavaScript et considérez passer à un navigateur web qui
|
|
<a href="https://videojs.com/html5-video-support/" target="_blank">supporte la vidéo HTML5</a>.
|
|
</p>
|
|
</video>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Video Stats -->
|
|
<div class="mt-4 flex items-center justify-between text-sm text-gray-600">
|
|
<div class="flex items-center space-x-4">
|
|
<div class="flex items-center space-x-3">
|
|
<span class="flex items-center" title="Vues uniques">
|
|
<Eye class="w-4 h-4 mr-1" />
|
|
{{ viewsCount }}
|
|
</span>
|
|
<span class="flex items-center" title="Replays">
|
|
<RotateCcw class="w-4 h-4 mr-1" />
|
|
{{ replaysCount }}
|
|
</span>
|
|
</div>
|
|
<span class="flex items-center">
|
|
<Clock class="w-4 h-4 mr-1" />
|
|
{{ formatDuration(duration) }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-2">
|
|
<button
|
|
@click="toggleLike"
|
|
class="flex items-center space-x-1 px-3 py-1 rounded-full transition-colors"
|
|
:class="isLiked ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
|
|
>
|
|
<Heart :class="isLiked ? 'fill-current' : ''" class="w-4 h-4" />
|
|
<span>{{ likesCount }}</span>
|
|
</button>
|
|
|
|
<button
|
|
@click="toggleComments"
|
|
class="flex items-center space-x-1 px-3 py-1 rounded-full bg-gray-100 text-gray-600 hover:bg-gray-200 transition-colors"
|
|
>
|
|
<MessageSquare class="w-4 h-4" />
|
|
<span>{{ commentsCount }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, onBeforeUnmount, watch, computed } from 'vue'
|
|
import videojs from 'video.js'
|
|
import 'video.js/dist/video-js.css'
|
|
import { Eye, Clock, Heart, MessageSquare, RotateCcw } from 'lucide-vue-next'
|
|
import { getMediaUrl } from '@/utils/axios'
|
|
|
|
const props = defineProps({
|
|
src: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
poster: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
description: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
duration: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
viewsCount: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
replaysCount: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
likesCount: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
commentsCount: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
isLiked: {
|
|
type: Boolean,
|
|
default: false
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['like', 'toggle-comments'])
|
|
|
|
const videoPlayer = ref(null)
|
|
const player = ref(null)
|
|
const currentVideoSrc = ref(null) // Track la source actuelle pour éviter les rechargements inutiles
|
|
|
|
// Computed properties pour les URLs
|
|
const videoUrl = computed(() => getMediaUrl(props.src))
|
|
const posterUrl = computed(() => getMediaUrl(props.poster))
|
|
|
|
function formatDuration(seconds) {
|
|
if (!seconds) return '--:--'
|
|
|
|
const minutes = Math.floor(seconds / 60)
|
|
const remainingSeconds = seconds % 60
|
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`
|
|
}
|
|
|
|
function toggleLike() {
|
|
emit('like')
|
|
}
|
|
|
|
function toggleComments() {
|
|
emit('toggle-comments')
|
|
}
|
|
|
|
// Fonction pour gérer les raccourcis clavier manuellement
|
|
function handleKeydown(e) {
|
|
if (!player.value) return;
|
|
|
|
// Ignorer si l'utilisateur tape dans un input
|
|
if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return;
|
|
|
|
switch(e.key) {
|
|
case 'ArrowLeft':
|
|
e.preventDefault();
|
|
player.value.currentTime(Math.max(0, player.value.currentTime() - 10));
|
|
break;
|
|
case 'ArrowRight':
|
|
e.preventDefault();
|
|
player.value.currentTime(Math.min(player.value.duration(), player.value.currentTime() + 10));
|
|
break;
|
|
case ' ':
|
|
case 'Space': // Espace pour pause/play
|
|
e.preventDefault();
|
|
if (player.value.paused()) {
|
|
player.value.play();
|
|
} else {
|
|
player.value.pause();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (videoPlayer.value) {
|
|
// Options de base pour Video.js
|
|
const options = {
|
|
controls: true,
|
|
fluid: true,
|
|
responsive: true,
|
|
playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 2],
|
|
// Désactiver les hotkeys natifs qui causent l'erreur passive listener
|
|
userActions: {
|
|
hotkeys: false
|
|
},
|
|
html5: {
|
|
vhs: {
|
|
overrideNative: true
|
|
},
|
|
nativeAudioTracks: false,
|
|
nativeVideoTracks: false
|
|
},
|
|
controlBar: {
|
|
skipButtons: {
|
|
forward: 10,
|
|
backward: 10
|
|
},
|
|
children: [
|
|
'playToggle',
|
|
'skipBackward',
|
|
'skipForward',
|
|
'volumePanel',
|
|
'currentTimeDisplay',
|
|
'timeDivider',
|
|
'durationDisplay',
|
|
'progressControl',
|
|
'playbackRateMenuButton',
|
|
'fullscreenToggle'
|
|
]
|
|
}
|
|
};
|
|
|
|
player.value = videojs(videoPlayer.value, options);
|
|
|
|
// Définir la source initiale après l'initialisation
|
|
if (videoUrl.value) {
|
|
player.value.src({ src: videoUrl.value, type: 'video/mp4' })
|
|
currentVideoSrc.value = videoUrl.value
|
|
}
|
|
|
|
// Error handling
|
|
player.value.on('error', (error) => {
|
|
console.error('Video.js error:', error)
|
|
})
|
|
|
|
// Ajouter l'écouteur d'événements clavier global
|
|
document.addEventListener('keydown', handleKeydown)
|
|
}
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
if (player.value) {
|
|
player.value.dispose()
|
|
}
|
|
// Retirer l'écouteur
|
|
document.removeEventListener('keydown', handleKeydown)
|
|
})
|
|
|
|
// Watch for src changes to reload video - amélioré pour éviter les rechargements inutiles
|
|
watch(() => videoUrl.value, (newUrl, oldUrl) => {
|
|
// Ne recharger que si l'URL a vraiment changé et que le player est prêt
|
|
if (player.value && newUrl && newUrl !== currentVideoSrc.value) {
|
|
const wasPlaying = !player.value.paused()
|
|
const currentTime = player.value.currentTime()
|
|
|
|
player.value.src({ src: newUrl, type: 'video/mp4' })
|
|
player.value.load()
|
|
currentVideoSrc.value = newUrl
|
|
|
|
// Restaurer la position si possible (optionnel)
|
|
player.value.ready(() => {
|
|
if (currentTime > 0 && currentTime < player.value.duration()) {
|
|
player.value.currentTime(currentTime)
|
|
}
|
|
if (wasPlaying) {
|
|
player.value.play().catch(() => {})
|
|
}
|
|
})
|
|
}
|
|
}, { immediate: false })
|
|
</script>
|
|
|
|
<style scoped>
|
|
.video-js {
|
|
aspect-ratio: 16/9;
|
|
/* Fix pour l'erreur "passive event listener" sur certains navigateurs */
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
.video-js .vjs-big-play-button {
|
|
display: none;
|
|
}
|
|
</style>
|