- Added detailed labels to the deployment script for IndeedHub, including title, version, description, license, icon, and repository URL. - Updated package dependencies in package.json and package-lock.json, including upgrading 'nostr-tools' to version 2.23.0 and adding 'axios' and '@tanstack/vue-query'. - Improved README with a modern description of the platform and updated project structure details. This commit enhances the clarity of the deployment process and ensures the project is using the latest dependencies for better performance and features.
482 lines
13 KiB
Vue
482 lines
13 KiB
Vue
<template>
|
|
<Transition name="player-fade">
|
|
<div v-if="isOpen" class="video-player-overlay">
|
|
<div class="video-player-container">
|
|
<!-- Close Button -->
|
|
<button @click="closePlayer" class="close-button">
|
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Video Area (Dummy Player) -->
|
|
<div class="video-area">
|
|
<img
|
|
v-if="content?.backdrop || content?.thumbnail"
|
|
:src="content?.backdrop || content?.thumbnail"
|
|
:alt="content?.title"
|
|
class="w-full h-full object-cover"
|
|
/>
|
|
|
|
<!-- Play Overlay -->
|
|
<div class="video-overlay">
|
|
<button class="play-button" @click="togglePlay">
|
|
<svg v-if="!isPlaying" class="w-20 h-20" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M8 5v14l11-7z" />
|
|
</svg>
|
|
<svg v-else class="w-20 h-20" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Dummy Notice -->
|
|
<div class="demo-notice">
|
|
<div class="demo-badge">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
|
</svg>
|
|
Demo Mode
|
|
</div>
|
|
<p class="text-sm">Video player preview - Full streaming coming soon</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Video Controls -->
|
|
<div class="video-controls">
|
|
<!-- Progress Bar -->
|
|
<div class="progress-container">
|
|
<div class="progress-bar">
|
|
<div class="progress-filled" :style="{ width: `${progress}%` }"></div>
|
|
<div class="progress-handle" :style="{ left: `${progress}%` }"></div>
|
|
</div>
|
|
<div class="time-display">
|
|
<span>{{ formatTime(currentTime) }}</span>
|
|
<span class="text-white/60">/</span>
|
|
<span class="text-white/60">{{ formatTime(duration) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Control Buttons -->
|
|
<div class="control-buttons">
|
|
<!-- Left Side -->
|
|
<div class="flex items-center gap-4">
|
|
<button @click="togglePlay" class="control-btn">
|
|
<svg v-if="!isPlaying" class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M8 5v14l11-7z" />
|
|
</svg>
|
|
<svg v-else class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
|
|
</svg>
|
|
</button>
|
|
|
|
<button class="control-btn">
|
|
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M5 4v16l7-8z M12 4v16l7-8z" />
|
|
</svg>
|
|
</button>
|
|
|
|
<button class="control-btn">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
|
|
</svg>
|
|
</button>
|
|
|
|
<div class="text-white font-medium text-sm">{{ content?.title }}</div>
|
|
</div>
|
|
|
|
<!-- Right Side -->
|
|
<div class="flex items-center gap-4">
|
|
<button class="control-btn">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
</svg>
|
|
</button>
|
|
|
|
<div class="quality-selector">
|
|
<button class="control-btn">
|
|
{{ quality }}
|
|
<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<button class="control-btn" @click="toggleFullscreen">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5l-5-5m5 5v-4m0 4h-4" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Content Info Panel -->
|
|
<div class="content-info-panel">
|
|
<h2 class="text-2xl font-bold text-white mb-2">{{ content?.title }}</h2>
|
|
<div class="flex items-center gap-4 text-sm text-white/80 mb-4">
|
|
<span v-if="content?.releaseYear" class="bg-white/10 px-2 py-0.5 rounded">{{ content.releaseYear }}</span>
|
|
<span v-if="content?.rating">{{ content.rating }}</span>
|
|
<span v-if="content?.duration">{{ content.duration }}min</span>
|
|
<span class="text-green-400 flex items-center gap-1">
|
|
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
|
</svg>
|
|
Cinephile Access
|
|
</span>
|
|
</div>
|
|
<p class="text-white/70 text-sm line-clamp-3">{{ content?.description }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, watch } from 'vue'
|
|
import type { Content } from '../types/content'
|
|
|
|
interface Props {
|
|
isOpen: boolean
|
|
content: Content | null
|
|
}
|
|
|
|
interface Emits {
|
|
(e: 'close'): void
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
const emit = defineEmits<Emits>()
|
|
|
|
const isPlaying = ref(false)
|
|
const progress = ref(0)
|
|
const currentTime = ref(0)
|
|
const duration = ref(7200) // 2 hours in seconds
|
|
const quality = ref('4K')
|
|
|
|
let playInterval: number | null = null
|
|
|
|
watch(() => props.isOpen, (newVal) => {
|
|
if (newVal) {
|
|
// Reset player state when opened
|
|
progress.value = 0
|
|
currentTime.value = 0
|
|
isPlaying.value = false
|
|
} else {
|
|
// Stop playing when closed
|
|
stopPlay()
|
|
}
|
|
})
|
|
|
|
function closePlayer() {
|
|
stopPlay()
|
|
emit('close')
|
|
}
|
|
|
|
function togglePlay() {
|
|
if (isPlaying.value) {
|
|
stopPlay()
|
|
} else {
|
|
startPlay()
|
|
}
|
|
}
|
|
|
|
function startPlay() {
|
|
isPlaying.value = true
|
|
console.log('▶️ Video playing (demo mode)')
|
|
|
|
// Simulate playback progress
|
|
playInterval = window.setInterval(() => {
|
|
currentTime.value += 1
|
|
progress.value = (currentTime.value / duration.value) * 100
|
|
|
|
// Loop when finished
|
|
if (currentTime.value >= duration.value) {
|
|
currentTime.value = 0
|
|
progress.value = 0
|
|
}
|
|
}, 1000)
|
|
}
|
|
|
|
function stopPlay() {
|
|
isPlaying.value = false
|
|
if (playInterval) {
|
|
clearInterval(playInterval)
|
|
playInterval = null
|
|
}
|
|
}
|
|
|
|
function toggleFullscreen() {
|
|
console.log('🖥️ Fullscreen toggled (demo)')
|
|
}
|
|
|
|
function formatTime(seconds: number): string {
|
|
const h = Math.floor(seconds / 3600)
|
|
const m = Math.floor((seconds % 3600) / 60)
|
|
const s = Math.floor(seconds % 60)
|
|
|
|
if (h > 0) {
|
|
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`
|
|
}
|
|
return `${m}:${s.toString().padStart(2, '0')}`
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.video-player-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 10000;
|
|
background: #0a0a0a;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.video-player-container {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
}
|
|
|
|
.close-button {
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 100;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 50%;
|
|
width: 48px;
|
|
height: 48px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.close-button:hover {
|
|
background: rgba(0, 0, 0, 0.9);
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.video-area {
|
|
flex: 1;
|
|
position: relative;
|
|
background: #000;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.video-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
opacity: 1;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.video-area:hover .video-overlay {
|
|
opacity: 1;
|
|
}
|
|
|
|
.play-button {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
color: white;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.play-button:hover {
|
|
background: rgba(255, 255, 255, 0.3);
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
transform: scale(1.1);
|
|
}
|
|
|
|
.demo-notice {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 20px;
|
|
background: rgba(247, 147, 26, 0.15);
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border: 1px solid rgba(247, 147, 26, 0.3);
|
|
border-radius: 12px;
|
|
padding: 12px 16px;
|
|
color: white;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.demo-badge {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
background: rgba(247, 147, 26, 0.3);
|
|
padding: 4px 12px;
|
|
border-radius: 8px;
|
|
font-weight: 600;
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: #F7931A;
|
|
}
|
|
|
|
.video-controls {
|
|
background: rgba(0, 0, 0, 0.85);
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
|
padding: 16px 24px;
|
|
}
|
|
|
|
.progress-container {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.progress-bar {
|
|
position: relative;
|
|
height: 6px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 3px;
|
|
cursor: pointer;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.progress-filled {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
height: 100%;
|
|
background: #F7931A;
|
|
border-radius: 3px;
|
|
transition: width 0.1s linear;
|
|
}
|
|
|
|
.progress-handle {
|
|
position: absolute;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 14px;
|
|
height: 14px;
|
|
background: white;
|
|
border-radius: 50%;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.progress-bar:hover .progress-handle {
|
|
opacity: 1;
|
|
}
|
|
|
|
.time-display {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 13px;
|
|
color: white;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.control-buttons {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.control-btn {
|
|
color: white;
|
|
background: transparent;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
padding: 8px;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.control-btn:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.quality-selector {
|
|
position: relative;
|
|
}
|
|
|
|
.quality-selector .control-btn {
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.content-info-panel {
|
|
position: absolute;
|
|
bottom: 100px;
|
|
left: 24px;
|
|
max-width: 600px;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
border-radius: 16px;
|
|
padding: 20px;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.video-player-container:hover .content-info-panel {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Transitions */
|
|
.player-fade-enter-active,
|
|
.player-fade-leave-active {
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.player-fade-enter-from,
|
|
.player-fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.demo-notice {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
}
|
|
|
|
.content-info-panel {
|
|
display: none;
|
|
}
|
|
|
|
.control-buttons .text-white {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|