This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/h5p-service/players/interactive-video-player.html
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

426 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive Video - BreakPilot H5P</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f5f5f5;
padding: 20px;
min-height: 100vh;
}
.container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 30px;
max-width: 1200px;
margin: 0 auto;
}
h1 {
color: #333;
margin-bottom: 12px;
font-size: 28px;
}
.description {
color: #6b7280;
margin-bottom: 24px;
font-size: 16px;
line-height: 1.6;
}
.video-wrapper {
position: relative;
background: #000;
border-radius: 12px;
overflow: hidden;
aspect-ratio: 16/9;
margin-bottom: 24px;
}
.video-wrapper iframe,
.video-wrapper video {
width: 100%;
height: 100%;
border: none;
}
.interaction-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
display: none;
align-items: center;
justify-content: center;
padding: 20px;
z-index: 100;
}
.interaction-overlay.show {
display: flex;
}
.interaction-content {
background: white;
border-radius: 12px;
padding: 32px;
max-width: 600px;
width: 100%;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.interaction-icon {
font-size: 48px;
text-align: center;
margin-bottom: 16px;
}
.interaction-title {
font-size: 24px;
font-weight: 700;
color: #1f2937;
margin-bottom: 16px;
text-align: center;
}
.interaction-text {
color: #4b5563;
font-size: 16px;
line-height: 1.6;
margin-bottom: 24px;
}
.interaction-link {
display: block;
padding: 12px 24px;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 8px;
text-align: center;
font-weight: 600;
margin-bottom: 12px;
}
.interaction-link:hover {
background: #5568d3;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
width: 100%;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.timeline {
background: #f9fafb;
border-radius: 8px;
padding: 20px;
margin-bottom: 24px;
}
.timeline-title {
font-weight: 700;
color: #374151;
margin-bottom: 12px;
font-size: 14px;
}
.timeline-markers {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.timeline-marker {
background: #fef3c7;
border: 2px solid #f59e0b;
border-radius: 6px;
padding: 6px 12px;
font-size: 12px;
font-weight: 600;
color: #92400e;
cursor: pointer;
transition: all 0.2s;
}
.timeline-marker:hover {
background: #fde68a;
transform: translateY(-2px);
}
.timeline-marker.completed {
background: #d1fae5;
border-color: #10b981;
color: #065f46;
}
.controls {
text-align: center;
color: #6b7280;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<h1 id="title"></h1>
<p class="description" id="description"></p>
<div class="video-wrapper">
<div id="videoContainer"></div>
<div class="interaction-overlay" id="interactionOverlay">
<div class="interaction-content">
<div class="interaction-icon" id="interactionIcon"></div>
<div class="interaction-title" id="interactionTitle"></div>
<div class="interaction-text" id="interactionText"></div>
<div id="interactionExtra"></div>
<button class="btn btn-primary" onclick="closeInteraction()">Weiter</button>
</div>
</div>
</div>
<div class="timeline">
<div class="timeline-title">📌 Interaktive Elemente im Video:</div>
<div class="timeline-markers" id="timelineMarkers"></div>
</div>
<div class="controls">
Das Video pausiert automatisch bei interaktiven Elementen
</div>
</div>
<script src="https://www.youtube.com/iframe_api"></script>
<script src="https://player.vimeo.com/api/player.js"></script>
<script>
let data = null;
let player = null;
let playerType = null;
let currentTime = 0;
let completedInteractions = new Set();
let checkInterval = null;
function loadContent() {
const urlParams = new URLSearchParams(window.location.search);
const dataParam = urlParams.get('data');
if (dataParam) {
try {
data = JSON.parse(decodeURIComponent(dataParam));
} catch (e) {
alert('Fehler beim Laden!');
return;
}
}
if (!data) {
alert('Keine Daten gefunden!');
return;
}
document.getElementById('title').textContent = data.title;
document.getElementById('description').textContent = data.description || '';
renderTimeline();
embedVideo();
}
function embedVideo() {
const container = document.getElementById('videoContainer');
const url = data.videoUrl;
// YouTube
if (url.includes('youtube.com') || url.includes('youtu.be')) {
const videoId = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&]+)/)?.[1];
if (videoId) {
playerType = 'youtube';
container.innerHTML = `<div id="ytPlayer"></div>`;
// YouTube API will be loaded via script tag
window.onYouTubeIframeAPIReady = function() {
player = new YT.Player('ytPlayer', {
videoId: videoId,
width: '100%',
height: '100%',
playerVars: {
autoplay: 0,
modestbranding: 1,
rel: 0
},
events: {
onReady: function() {
startTimeTracking();
},
onStateChange: function(event) {
if (event.data === YT.PlayerState.PLAYING) {
startTimeTracking();
}
}
}
});
};
}
}
// Vimeo
else if (url.includes('vimeo.com')) {
const videoId = url.match(/vimeo\.com\/(\d+)/)?.[1];
if (videoId) {
playerType = 'vimeo';
container.innerHTML = `<iframe id="vimeoPlayer" src="https://player.vimeo.com/video/${videoId}" allowfullscreen></iframe>`;
setTimeout(() => {
player = new Vimeo.Player('vimeoPlayer');
player.on('timeupdate', function(data) {
currentTime = data.seconds;
checkInteractions();
});
}, 500);
}
}
// Direct MP4
else if (url.endsWith('.mp4')) {
playerType = 'html5';
container.innerHTML = `<video id="html5Player" controls style="width: 100%; height: 100%;"><source src="${url}" type="video/mp4"></video>`;
const video = document.getElementById('html5Player');
video.addEventListener('timeupdate', function() {
currentTime = video.currentTime;
checkInteractions();
});
player = video;
}
}
function startTimeTracking() {
if (checkInterval) clearInterval(checkInterval);
checkInterval = setInterval(() => {
if (playerType === 'youtube' && player && player.getCurrentTime) {
currentTime = player.getCurrentTime();
checkInteractions();
}
}, 500);
}
function checkInteractions() {
data.interactions.forEach((interaction, index) => {
const interactionTime = timeToSeconds(interaction.time);
// Check if we're at this interaction time (within 1 second window)
if (Math.abs(currentTime - interactionTime) < 0.5 && !completedInteractions.has(index)) {
showInteraction(interaction, index);
}
});
}
function showInteraction(interaction, index) {
// Pause video
if (playerType === 'youtube' && player && player.pauseVideo) {
player.pauseVideo();
} else if (playerType === 'vimeo' && player && player.pause) {
player.pause();
} else if (playerType === 'html5' && player && player.pause) {
player.pause();
}
// Show overlay
const overlay = document.getElementById('interactionOverlay');
const icon = document.getElementById('interactionIcon');
const title = document.getElementById('interactionTitle');
const text = document.getElementById('interactionText');
const extra = document.getElementById('interactionExtra');
// Set icon based on type
switch (interaction.type) {
case 'question':
icon.textContent = '❓';
break;
case 'info':
icon.textContent = '';
break;
case 'link':
icon.textContent = '🔗';
break;
}
title.textContent = interaction.title;
text.textContent = interaction.content;
// Add link if type is link
if (interaction.type === 'link') {
extra.innerHTML = `<a href="${interaction.content}" target="_blank" class="interaction-link">🔗 Link öffnen</a>`;
} else {
extra.innerHTML = '';
}
overlay.classList.add('show');
completedInteractions.add(index);
// Update timeline
renderTimeline();
}
function closeInteraction() {
const overlay = document.getElementById('interactionOverlay');
overlay.classList.remove('show');
// Resume video
if (playerType === 'youtube' && player && player.playVideo) {
player.playVideo();
} else if (playerType === 'vimeo' && player && player.play) {
player.play();
} else if (playerType === 'html5' && player && player.play) {
player.play();
}
}
function renderTimeline() {
const container = document.getElementById('timelineMarkers');
container.innerHTML = '';
data.interactions.forEach((interaction, index) => {
const marker = document.createElement('div');
marker.className = 'timeline-marker';
if (completedInteractions.has(index)) {
marker.classList.add('completed');
}
const iconMap = {
question: '❓',
info: '',
link: '🔗'
};
marker.textContent = `${iconMap[interaction.type]} ${interaction.time} - ${interaction.title}`;
marker.onclick = () => seekTo(interaction.time);
container.appendChild(marker);
});
}
function seekTo(timeStr) {
const seconds = timeToSeconds(timeStr);
if (playerType === 'youtube' && player && player.seekTo) {
player.seekTo(seconds, true);
} else if (playerType === 'vimeo' && player && player.setCurrentTime) {
player.setCurrentTime(seconds);
} else if (playerType === 'html5' && player) {
player.currentTime = seconds;
}
}
function timeToSeconds(timeStr) {
const parts = timeStr.split(':');
const minutes = parseInt(parts[0]) || 0;
const seconds = parseInt(parts[1]) || 0;
return minutes * 60 + seconds;
}
loadContent();
</script>
</body>
</html>