382 lines
20 KiB
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'; ?>
|