churchtube/watch.php

423 lines
21 KiB
PHP

<?php
require_once 'includes/db.php';
require_once 'includes/settings_helper.php';
require_once 'includes/auth.php';
$video_id = $_GET['id'] ?? 0;
$stmt = $pdo->prepare("SELECT v.*, u.username as uploader FROM videos v JOIN users u ON v.uploader_id = u.id WHERE v.id = ?");
$stmt->execute([$video_id]);
$video = $stmt->fetch();
if (!$video) {
header('Location: index.php');
exit;
}
// Get Recommendations (videos with same tags)
$stmt = $pdo->prepare("
SELECT DISTINCT v.* FROM videos v
JOIN video_tags vt ON v.id = vt.video_id
WHERE vt.tag_id IN (SELECT tag_id FROM video_tags WHERE video_id = ?)
AND v.id != ?
LIMIT 4
");
$stmt->execute([$video_id, $video_id]);
$recommendations = $stmt->fetchAll();
if (empty($recommendations)) {
$stmt = $pdo->prepare("SELECT * FROM videos WHERE id != ? ORDER BY created_at DESC LIMIT 4");
$stmt->execute([$video_id]);
$recommendations = $stmt->fetchAll();
}
// Fetch comments with reaction counts
$stmt = $pdo->prepare("
SELECT c.*, u.username,
(SELECT COUNT(*) FROM reactions WHERE comment_id = c.id AND reaction_type = 'thumb') as thumbs,
(SELECT COUNT(*) FROM reactions WHERE comment_id = c.id AND reaction_type = 'heart') as hearts,
(SELECT COUNT(*) FROM reactions WHERE comment_id = c.id AND reaction_type = 'pray') as prays,
(SELECT COUNT(*) FROM reactions WHERE comment_id = c.id AND reaction_type = 'insight') as insights,
(SELECT COUNT(*) FROM reactions WHERE comment_id = c.id AND reaction_type = 'clap') as claps
FROM comments c JOIN users u ON c.user_id = u.id
WHERE c.video_id = ? ORDER BY c.created_at DESC
");
$stmt->execute([$video_id]);
$comments = $stmt->fetchAll();
require_once 'includes/header.php';
?>
<div id="watch-container" style="max-width: 1200px; margin: 0 auto; padding: 24px; display: grid; grid-template-columns: 1fr 350px; gap: 32px;">
<style>
@media (max-width: 900px) {
#watch-container {
grid-template-columns: 1fr !important;
}
#sidebar-recommendations {
order: 2;
}
}
</style>
<!-- Main Content -->
<div>
<!-- Video Player Area -->
<div id="video-wrapper" style="background: #000; border-radius: 12px; overflow: hidden; margin-bottom: 24px; box-shadow: 0 10px 40px rgba(0,0,0,0.6); position: relative; width: 100%;">
<?php if ($video['source_type'] === 'upload'): ?>
<div style="aspect-ratio: 16/9;">
<video id="main-video" controls onplay="incrementViews(<?= $video_id ?>)" style="width: 100%; height: 100%; object-fit: contain;" poster="<?= $video['thumbnail_url'] ?: 'assets/images/default_thumb.png' ?>">
<source src="<?= htmlspecialchars($video['video_url']) ?>" type="video/mp4">
</video>
</div>
<?php else: ?>
<!-- Play Overlay for Linked Videos -->
<div id="external-overlay" style="aspect-ratio: 16/9; background-image: url('<?= $video['thumbnail_url'] ?: 'assets/images/default_thumb.png' ?>'); background-size: cover; background-position: center; display: flex; align-items: center; justify-content: center; cursor: pointer;" onclick="loadExternalVideo()">
<div style="background: rgba(var(--primary-rgb, 124, 77, 255), 0.9); width: 80px; height: 80px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; box-shadow: 0 0 30px rgba(0,0,0,0.5);">
<i class="fas fa-play" style="margin-left: 5px;"></i>
</div>
</div>
<div id="iframe-container" style="display: none; position: relative; width: 100%; padding-bottom: 56.25%; height: 0; background: #000; min-height: 300px;">
<iframe id="external-iframe" src="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; overflow: hidden;"></iframe>
</div>
<?php endif; ?>
</div>
<!-- Video Info -->
<div style="margin-bottom: 32px;">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<h1 style="font-size: 1.8rem; margin-bottom: 12px;"><?= htmlspecialchars($video['title']) ?></h1>
<?php if (isEditor()): ?>
<a href="admin/edit_video.php?id=<?= $video_id ?>" class="btn" style="background: var(--glass);"><i class="fas fa-edit"></i> Edit</a>
<?php endif; ?>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--glass-border); padding-bottom: 20px;">
<div style="color: var(--text-muted); font-size: 0.95rem;">
<?= number_format($video['views']) ?> views • <?= date('M d, Y', strtotime($video['release_date'])) ?>
</div>
<div style="display: flex; gap: 12px; position: relative;">
<button onclick="toggleShareMenu()" class="btn" style="background: var(--primary-color); padding: 8px 16px;">
<i class="fas fa-share"></i> Share
</button>
<?php if (isLoggedIn()):
$stmt = $pdo->prepare("SELECT 1 FROM bookmarks WHERE user_id = ? AND video_id = ?");
$stmt->execute([$_SESSION['user_id'], $video_id]);
$is_bookmarked = $stmt->fetch();
?>
<button onclick="toggleBookmark(<?= $video_id ?>)" id="bookmark-btn" class="btn" style="background: rgba(255,255,255,0.15); color: #fff; padding: 8px 16px; border: 1px solid rgba(255,255,255,0.2);">
<i class="<?= $is_bookmarked ? 'fas' : 'far' ?> fa-bookmark"></i> <?= $is_bookmarked ? 'Bookmarked' : 'Bookmark' ?>
</button>
<?php endif; ?>
</div>
</div>
<div style="padding: 20px 0;">
<div style="display: flex; gap: 16px; align-items: center; margin-bottom: 16px;">
<?php
$u_stmt = $pdo->prepare("SELECT avatar_url FROM users WHERE id = ?");
$u_stmt->execute([$video['uploader_id']]);
$uploader_avatar = $u_stmt->fetchColumn();
?>
<div style="width: 48px; height: 48px; background: var(--primary-color); border-radius: 50%; display: flex; align-items: center; justify-content: center; overflow: hidden; border: 2px solid var(--glass-border);">
<?php if ($uploader_avatar): ?>
<img src="<?= htmlspecialchars($uploader_avatar) ?>" style="width: 100%; height: 100%; object-fit: cover;">
<?php else: ?>
<span style="font-weight: bold; color: white;"><?= strtoupper(substr($video['uploader'], 0, 1)) ?></span>
<?php endif; ?>
</div>
<div>
<div style="font-weight: 600;"><?= htmlspecialchars($video['uploader']) ?></div>
</div>
</div>
<div style="background: var(--glass); padding: 16px; border-radius: 12px; white-space: pre-wrap; line-height: 1.6;"><?= htmlspecialchars($video['description']) ?></div>
</div>
</div>
<!-- Comments Section -->
<div>
<h3 id="comment-count"><?= count($comments) ?> Comments</h3>
<?php if (isLoggedIn()): ?>
<form id="comment-form" style="margin: 24px 0; display: flex; gap: 16px;">
<input type="hidden" name="video_id" value="<?= $video_id ?>">
<textarea name="comment" id="comment-text" class="form-control" placeholder="Add a comment..." style="min-height: 80px;"></textarea>
<button type="submit" class="btn btn-primary" id="submit-comment" style="height: fit-content; align-self: flex-end;">Post</button>
</form>
<?php endif; ?>
<div id="comment-list" style="display: flex; flex-direction: column; gap: 24px;">
<?php foreach ($comments as $c): ?>
<div class="comment-item" style="display: flex; gap: 16px;">
<div style="width: 40px; height: 40px; background: #333; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; border: 1px solid var(--glass-border);">
<?php if ($c['avatar_url']): ?>
<img src="<?= htmlspecialchars($c['avatar_url']) ?>" style="width: 100%; height: 100%; object-fit: cover;">
<?php else: ?>
<span style="font-weight: bold;"><?= strtoupper(substr($c['username'], 0, 1)) ?></span>
<?php endif; ?>
</div>
<div style="flex-grow: 1;">
<div style="font-weight: 600; font-size: 0.9rem;">
<?= htmlspecialchars($c['username']) ?>
<span style="font-weight: 400; color: var(--text-muted); margin-left: 8px; font-size: 0.8rem;"><?= date('M d, Y', strtotime($c['created_at'])) ?></span>
</div>
<div style="margin-top: 4px;"><?= htmlspecialchars($c['comment_text']) ?></div>
<!-- Reactions & Actions -->
<div style="display: flex; gap: 16px; margin-top: 10px; align-items: center; font-size: 0.85rem; color: var(--text-muted);">
<span onclick="react(<?= $c['id'] ?>, 'thumb')" style="cursor:pointer;"><i class="fas fa-thumbs-up"></i> <span id="count-thumb-<?= $c['id'] ?>"><?= $c['thumbs'] ?></span></span>
<span onclick="react(<?= $c['id'] ?>, 'heart')" style="cursor:pointer;"><i class="fas fa-heart"></i> <span id="count-heart-<?= $c['id'] ?>"><?= $c['hearts'] ?></span></span>
<span onclick="react(<?= $c['id'] ?>, 'pray')" style="cursor:pointer;"><i class="fas fa-hands-praying"></i> <span id="count-pray-<?= $c['id'] ?>"><?= $c['prays'] ?></span></span>
<span onclick="react(<?= $c['id'] ?>, 'insight')" style="cursor:pointer;"><i class="fas fa-lightbulb"></i> <span id="count-insight-<?= $c['id'] ?>"><?= $c['insights'] ?></span></span>
<span onclick="react(<?= $c['id'] ?>, 'clap')" style="cursor:pointer;"><i class="fas fa-hands-clapping"></i> <span id="count-clap-<?= $c['id'] ?>"><?= $c['claps'] ?></span></span>
<span onclick="report(<?= $c['id'] ?>)" style="cursor:pointer; margin-left: 10px; font-weight: 600; color: #ff4081;">REPORT</span>
<?php if (isLoggedIn() && ($c['user_id'] == $_SESSION['user_id'] || isModerator())): ?>
<span onclick="deleteComment(<?= $c['id'] ?>)" style="cursor:pointer; margin-left: 10px; color: #ff4081;"><i class="fas fa-trash"></i></span>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<!-- Sidebar Recommendations -->
<div style="display: flex; flex-direction: column; gap: 20px;">
<h3 style="font-size: 1.1rem;">Recommended</h3>
<?php foreach ($recommendations as $rec): ?>
<a href="watch.php?id=<?= $rec['id'] ?>" style="display: flex; gap: 12px;">
<div style="width: 140px; aspect-ratio: 16/9; background: #333; background-image: url('<?= $rec['thumbnail_url'] ?: 'assets/images/default_thumb.png' ?>'); background-size: cover; border-radius: 8px; flex-shrink: 0;"></div>
<div>
<div style="font-weight: 600; font-size: 0.9rem; line-height: 1.2; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;">
<?= htmlspecialchars($rec['title']) ?>
</div>
<div style="font-size: 0.8rem; color: var(--text-muted); margin-top: 4px;">
<?= number_format($rec['views']) ?> views
</div>
</div>
</a>
<?php endforeach; ?>
</div>
</div>
<script>
const videoId = <?= $video_id ?>;
const currentUserId = <?= $_SESSION['user_id'] ?? 0 ?>;
const isLoggedIn = <?= isLoggedIn() ? 'true' : 'false' ?>;
const isModerator = <?= isModerator() ? 'true' : 'false' ?>;
const videoElem = document.getElementById('main-video');
const commentForm = document.getElementById('comment-form');
const commentList = document.getElementById('comment-list');
const commentCount = document.getElementById('comment-count');
let currentCommentsJson = '';
if (videoElem) {
videoElem.onended = () => {
document.getElementById('video-end-overlay').style.display = 'flex';
};
}
function loadExternalVideo() {
const overlay = document.getElementById('external-overlay');
const container = document.getElementById('iframe-container');
const iframe = document.getElementById('external-iframe');
let videoUrl = "<?= htmlspecialchars($video['video_url']) ?>";
// Auto-convert Google Drive links to preview mode for better mobile support
if (videoUrl.includes('drive.google.com') && videoUrl.includes('/view')) {
videoUrl = videoUrl.replace('/view', '/preview');
}
incrementViews(videoId);
overlay.style.display = 'none';
container.style.display = 'block';
iframe.src = videoUrl;
}
let viewsIncremented = false;
async function incrementViews(id) {
if (viewsIncremented) return;
viewsIncremented = true;
try {
await fetch('api/increment_views.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `video_id=${id}`
});
} catch (e) {
console.error('Failed to increment views');
}
}
async function toggleBookmark(id) {
let timestamp = 0;
if (videoElem) {
timestamp = videoElem.currentTime;
}
const res = await fetch('api/toggle_bookmark.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `video_id=${id}&timestamp=${timestamp}`
});
const data = await res.json();
if (data.success) {
const btn = document.getElementById('bookmark-btn');
const isBookmarked = data.action === 'added';
btn.innerHTML = `<i class="${isBookmarked ? 'fas' : 'far'} fa-bookmark"></i> ${isBookmarked ? 'Bookmarked' : 'Bookmark'}`;
}
}
// Deep linking to timestamp
window.addEventListener('load', () => {
const urlParams = new URLSearchParams(window.location.search);
const t = urlParams.get('t');
if (t && videoElem) {
videoElem.currentTime = parseFloat(t);
videoElem.play().catch(() => {}); // Autoplay might be blocked
}
});
async function react(commentId, type) {
const res = await fetch('api/react.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `comment_id=${commentId}&type=${type}`
});
const result = await res.json();
if (result.success) {
document.getElementById(`count-${type}-${commentId}`).innerText = result.count;
}
}
async function report(commentId) {
if (!confirm('Report this comment for moderation?')) return;
const res = await fetch('api/report.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `comment_id=${commentId}`
});
const result = await res.json();
if (result.success) updateComments();
}
async function deleteComment(commentId) {
if (!confirm('Moderator: Delete this comment permanently?')) return;
const res = await fetch('api/delete_comment.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: `comment_id=${commentId}`
});
const result = await res.json();
if (result.success) updateComments();
}
async function updateComments() {
try {
const response = await fetch(`api/get_comments.php?video_id=${videoId}`);
const comments = await response.json();
const json = JSON.stringify(comments);
if (json !== currentCommentsJson) {
renderComments(comments);
currentCommentsJson = json;
commentCount.innerText = `${comments.length} Comments`;
}
} catch (error) {
console.error('Failed to fetch comments:', error);
}
}
function renderComments(comments) {
commentList.innerHTML = '';
comments.forEach(c => {
const div = document.createElement('div');
div.id = `comment-${c.id}`;
div.style.display = 'flex';
div.style.gap = '16px';
div.style.marginBottom = '24px';
const avatarHtml = c.avatar_url
? `<img src="${c.avatar_url}" style="width: 100%; height: 100%; object-fit: cover;">`
: `<span style="font-weight: bold;">${c.username.charAt(0).toUpperCase()}</span>`;
div.innerHTML = `
<div style="width: 40px; height: 40px; background: #333; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; border: 1px solid var(--glass-border);">
${avatarHtml}
</div>
<div style="flex-grow: 1;">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div style="font-weight: 600; font-size: 0.9rem;">
${escapeHtml(c.username)}
<span style="font-weight: 400; color: var(--text-muted); margin-left: 8px; font-size: 0.8rem;">${c.display_date}</span>
</div>
<div style="display: flex; gap: 12px; align-items: center;">
${(isLoggedIn && (c.user_id == currentUserId || isModerator)) ?
`<button onclick="deleteComment(${c.id})" class="btn" style="background: none; padding: 4px; color: #ff4081;" title="Delete"><i class="fas fa-trash"></i></button>` : ''}
<button onclick="report(${c.id})" class="btn" style="background: none; padding: 4px; color: #ff4081; font-weight: 600; font-size: 0.75rem;">REPORT</button>
</div>
</div>
<div style="margin-top: 4px; line-height: 1.4;">${escapeHtml(c.comment_text)}</div>
<div style="display: flex; gap: 16px; margin-top: 10px; align-items: center; font-size: 0.85rem; color: var(--text-muted);">
<span onclick="react(${c.id}, 'thumb')" style="cursor:pointer;"><i class="fas fa-thumbs-up"></i> <span id="count-thumb-${c.id}">${c.thumbs}</span></span>
<span onclick="react(${c.id}, 'heart')" style="cursor:pointer;"><i class="fas fa-heart"></i> <span id="count-heart-${c.id}">${c.hearts}</span></span>
<span onclick="react(${c.id}, 'pray')" style="cursor:pointer;"><i class="fas fa-hands-praying"></i> <span id="count-pray-${c.id}">${c.prays}</span></span>
<span onclick="react(${c.id}, 'insight')" style="cursor:pointer;"><i class="fas fa-lightbulb"></i> <span id="count-insight-${c.id}">${c.insights}</span></span>
<span onclick="react(${c.id}, 'clap')" style="cursor:pointer;"><i class="fas fa-hands-clapping"></i> <span id="count-clap-${c.id}">${c.claps}</span></span>
</div>
</div>
`;
commentList.appendChild(div);
});
}
function toggleShareMenu() {
const menu = document.getElementById('share-menu');
menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
}
function copyCurrentLink() {
const url = window.location.href;
navigator.clipboard.writeText(url).then(() => {
alert('Link copied to clipboard!');
toggleShareMenu();
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
commentForm?.addEventListener('submit', async (e) => {
e.preventDefault();
const text = document.getElementById('comment-text');
const btn = document.getElementById('submit-comment');
if (!text.value.trim()) return;
btn.disabled = true;
const formData = new FormData();
formData.append('video_id', videoId);
formData.append('comment', text.value);
const res = await fetch('api/post_comment.php', { method: 'POST', body: formData });
const result = await res.json();
if (result.success) {
text.value = '';
await updateComments();
}
btn.disabled = false;
});
// Initialize polling
setInterval(updateComments, 5000);
updateComments(); // Initial load
</script>
<?php require_once 'includes/footer.php'; ?>