churchtube/watch.php

382 lines
20 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; aspect-ratio: 16/9; 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'): ?>
<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>
<?php else: ?>
<!-- Play Overlay for Linked Videos -->
<div id="external-overlay" style="position: absolute; inset: 0; 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; z-index: 5;" 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); transition: transform 0.2s;" onmouseover="this.style.transform='scale(1.1)'" onmouseout="this.style.transform='scale(1)'">
<i class="fas fa-play" style="margin-left: 5px;"></i>
</div>
</div>
<div id="iframe-container" style="width: 100%; height: 100%; display: none;">
<iframe id="external-iframe" width="100%" height="100%" src="" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="width: 100%; height: 100%; object-fit: contain;"></iframe>
</div>
<?php endif; ?>
<!-- Recommendation Overlay (for uploaded videos) -->
<div id="video-end-overlay" style="display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.8); z-index: 10; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 20px;">
<h3 style="margin-bottom: 20px;">Up Next</h3>
<div style="display: flex; gap: 16px;">
<?php foreach (array_slice($recommendations, 0, 2) as $rec): ?>
<a href="watch.php?id=<?= $rec['id'] ?>" style="width: 200px;">
<div style="aspect-ratio: 16/9; background: #333; background-image: url('<?= $rec['thumbnail_url'] ?>'); background-size: cover; border-radius: 8px;"></div>
<div style="margin-top: 8px; font-size: 0.9rem;"><?= htmlspecialchars($rec['title']) ?></div>
</a>
<?php endforeach; ?>
</div>
<button onclick="document.getElementById('video-end-overlay').style.display='none'" class="btn" style="margin-top: 20px; background: var(--glass);">Replay</button>
</div>
</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 class="btn" onclick="toggleShareMenu()" style="background: var(--primary-color); color: white;"><i class="fas fa-share"></i> Share</button>
<!-- Share Menu Dropdown -->
<div id="share-menu" style="display: none; position: absolute; top: 100%; right: 0; background: var(--bg-card); border: 1px solid var(--glass-border); border-radius: 8px; padding: 12px; z-index: 100; min-width: 200px; margin-top: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.5);">
<div style="margin-bottom: 12px; font-weight: 600; font-size: 0.9rem;">Share this sermon</div>
<div style="display: grid; gap: 8px;">
<button onclick="copyCurrentLink()" class="btn" style="background: var(--glass); width: 100%; justify-content: flex-start; font-size: 0.85rem;">
<i class="fas fa-link" style="margin-right: 8px;"></i> Copy Link
</button>
<a href="https://www.facebook.com/sharer/sharer.php?u=<?= urlencode((isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']) ?>" target="_blank" class="btn" style="background: #1877f2; width: 100%; justify-content: flex-start; font-size: 0.85rem; text-decoration: none; color: white;">
<i class="fab fa-facebook" style="margin-right: 8px;"></i> Facebook
</a>
<a href="https://twitter.com/intent/tweet?url=<?= urlencode((isset($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']) ?>&text=Check out this sermon on ChurchTube!" target="_blank" class="btn" style="background: #000; width: 100%; justify-content: flex-start; font-size: 0.85rem; text-decoration: none; color: white;">
<i class="fab fa-x-twitter" style="margin-right: 8px;"></i> Twitter
</a>
</div>
</div>
</div>
</div>
<div style="padding: 20px 0;">
<div style="display: flex; gap: 16px; align-items: center; margin-bottom: 16px;">
<div style="width: 48px; height: 48px; background: var(--primary-color); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold;">
<?= strtoupper(substr($video['uploader'], 0, 1)) ?>
</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;"><?= 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; font-weight: bold;">
<?= strtoupper(substr($c['username'], 0, 1)) ?>
</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 (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 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');
const videoUrl = "<?= htmlspecialchars($video['video_url']) ?>";
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 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.className = 'comment-item';
div.style.display = 'flex';
div.style.gap = '16px';
div.innerHTML = `
<div style="width: 40px; height: 40px; background: #333; border-radius: 50%; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-weight: bold;">
${c.username.charAt(0).toUpperCase()}
</div>
<div style="flex-grow: 1;">
<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="margin-top: 4px;">${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>
<span onclick="report(${c.id})" style="cursor:pointer; margin-left: 10px; font-weight: 600; color: #ff4081;">REPORT</span>
${isModerator ? `<span onclick="deleteComment(${c.id})" style="cursor:pointer; margin-left: 10px; color: #ff4081;"><i class="fas fa-trash"></i></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'; ?>