quad-tap
Version:
A pure JavaScript implementation of the Quad-Tap overlay interaction for videos with advanced video player API integration
623 lines (529 loc) • 22.1 kB
HTML
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>QuadTap Video API Integration Test</title><style>body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
margin-top: 0;
}
.video-container {
position: relative;
width: 100%;
max-width: 800px;
margin: 20px auto;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
video {
width: 100%;
display: block;
}
.controls {
margin: 20px 0;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
border: 1px solid #ddd;
}
button {
padding: 8px 16px;
margin-right: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
button:hover {
background-color: #45a049;
}
.event-log {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
border: 1px solid #ddd;
max-height: 300px;
overflow-y: auto;
}
.event-log h3 {
margin-top: 0;
}
.log-entry {
margin-bottom: 5px;
padding: 5px;
border-bottom: 1px solid #eee;
font-family: monospace;
}
.log-entry:last-child {
border-bottom: none;
}
.log-time {
color: #999;
font-size: 0.8em;
}
.log-type {
font-weight: bold;
margin-right: 5px;
}
.log-type.info {
color: #2196F3;
}
.log-type.error {
color: #f44336;
}
.log-type.warn {
color: #ff9800;
}
.log-type.success {
color: #4CAF50;
}
.storage-viewer {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
border: 1px solid #ddd;
}
.storage-viewer h3 {
margin-top: 0;
}
.storage-item {
margin-bottom: 5px;
padding: 5px;
border-bottom: 1px solid #eee;
font-family: monospace;
}
.storage-key {
font-weight: bold;
color: #2196F3;
}
.storage-value {
color: #333;
}
.coordinates-display {
position: fixed;
bottom: 10px;
right: 10px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
z-index: 9999;
}
.video-state {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
border: 1px solid #ddd;
}
.video-state h3 {
margin-top: 0;
}
.state-item {
margin-bottom: 5px;
padding: 5px;
border-bottom: 1px solid #eee;
font-family: monospace;
}
.state-key {
font-weight: bold;
color: #2196F3;
display: inline-block;
width: 150px;
}
.state-value {
color: #333;
}
.state-value.playing {
color: #4CAF50;
font-weight: bold;
}
.state-value.paused {
color: #f44336;
font-weight: bold;
}
.coordinate-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1000;
opacity: 0.5;
display: none;
}
.coordinate-grid.active {
display: block;
}
.grid-line-h {
position: absolute;
left: 0;
width: 100%;
height: 1px;
background-color: rgba(255, 255, 255, 0.3);
}
.grid-line-v {
position: absolute;
top: 0;
width: 1px;
height: 100%;
background-color: rgba(255, 255, 255, 0.3);
}
.grid-center {
position: absolute;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
background-color: rgba(255, 0, 0, 0.5);
border-radius: 50%;
transform: translate(-50%, -50%);
}
.quad-tap-controls {
position: absolute;
bottom: 10%;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
background-color: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
}
.quad-tap-control-button {
background-color: transparent;
border: none;
color: white;
font-size: 24px;
margin: 0 10px;
cursor: pointer;
padding: 5px;
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
}
.quad-tap-control-button:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.quad-tap-emoji {
position: absolute;
font-size: 24px;
z-index: 1000;
}
.quad-tap-emoji-ne {
top: 5%;
right: 5%;
}
.quad-tap-emoji-nw {
top: 5%;
left: 5%;
}
.quad-tap-emoji-se {
bottom: 5%;
right: 5%;
}
.quad-tap-emoji-sw {
bottom: 5%;
left: 5%;
}</style><script src="quad-tap.js"></script></head><body><div class="container"><h1>QuadTap Video API Integration Test</h1><p>This page demonstrates the integration of QuadTap with video player APIs, ensuring the video only pauses when the lightbox is open.</p><div class="video-container" id="main-video-container"><video id="main-video" controls data-video-id="test-video-123"><source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4">Your browser does not support the video tag.</video><div class="coordinate-grid" id="coordinate-grid"><div class="grid-line-h" style="top: 25%"></div><div class="grid-line-h" style="top: 50%"></div><div class="grid-line-h" style="top: 75%"></div><div class="grid-line-v" style="left: 25%"></div><div class="grid-line-v" style="left: 50%"></div><div class="grid-line-v" style="left: 75%"></div><div class="grid-center"></div></div></div><div class="controls"><button id="init-btn">Initialize QuadTap</button> <button id="destroy-btn">Destroy QuadTap</button> <button id="clear-log-btn">Clear Log</button> <button id="clear-storage-btn">Clear Storage</button> <button id="toggle-grid-btn">Toggle Grid</button></div><div class="video-state"><h3>Video State</h3><div id="video-state-container"><div class="state-item"><span class="state-key">Playing:</span> <span class="state-value" id="video-playing">false</span></div><div class="state-item"><span class="state-key">Current Time:</span> <span class="state-value" id="video-current-time">0</span></div><div class="state-item"><span class="state-key">Duration:</span> <span class="state-value" id="video-duration">0</span></div><div class="state-item"><span class="state-key">Volume:</span> <span class="state-value" id="video-volume">0</span></div><div class="state-item"><span class="state-key">Muted:</span> <span class="state-value" id="video-muted">false</span></div><div class="state-item"><span class="state-key">Playback Rate:</span> <span class="state-value" id="video-playback-rate">1</span></div></div></div><div class="event-log"><h3>Event Log</h3><div id="log-container"></div></div><div class="storage-viewer"><h3>Local Storage</h3><div id="storage-container"></div></div></div><div class="coordinates-display" id="coordinates-display">X: 0, Y: 0</div><script src="../dist/quad-tap.js"></script><script>document.addEventListener('DOMContentLoaded', function() {
// Initialize elements
const initBtn = document.getElementById('init-btn');
const destroyBtn = document.getElementById('destroy-btn');
const clearLogBtn = document.getElementById('clear-log-btn');
const clearStorageBtn = document.getElementById('clear-storage-btn');
const toggleGridBtn = document.getElementById('toggle-grid-btn');
const logContainer = document.getElementById('log-container');
const storageContainer = document.getElementById('storage-container');
const coordinatesDisplay = document.getElementById('coordinates-display');
const videoElement = document.getElementById('main-video');
const videoContainer = document.getElementById('main-video-container');
const coordinateGrid = document.getElementById('coordinate-grid');
// Video state elements
const videoPlayingElement = document.getElementById('video-playing');
const videoCurrentTimeElement = document.getElementById('video-current-time');
const videoDurationElement = document.getElementById('video-duration');
const videoVolumeElement = document.getElementById('video-volume');
const videoMutedElement = document.getElementById('video-muted');
const videoPlaybackRateElement = document.getElementById('video-playback-rate');
// Toggle grid
toggleGridBtn.addEventListener('click', function() {
coordinateGrid.classList.toggle('active');
});
// Track mouse coordinates
videoContainer.addEventListener('mousemove', function(evt) {
const rect = videoContainer.getBoundingClientRect();
const x = evt.clientX - rect.left;
const y = evt.clientY - rect.top;
// Get container dimensions
const containerWidth = rect.width;
const containerHeight = rect.height;
// Calculate normalized coordinates
let normalizedX = 0;
let normalizedY = 0;
let percentX = 0;
let percentY = 0;
// Check if QuadTap and Coordinates are available
if (typeof QuadTap !== 'undefined' && typeof QuadTap.Coordinates !== 'undefined') {
const normalized = QuadTap.Coordinates.normalize(x, y, containerWidth, containerHeight);
normalizedX = normalized.normalizedX;
normalizedY = normalized.normalizedY;
const percentage = QuadTap.Coordinates.toPercentage(x, y, containerWidth, containerHeight);
percentX = percentage.percentX;
percentY = percentage.percentY;
} else {
// Fallback if Coordinates utility is not available
normalizedX = containerWidth > 0 ? x / containerWidth : 0;
normalizedY = containerHeight > 0 ? y / containerHeight : 0;
percentX = normalizedX * 100;
percentY = normalizedY * 100;
}
// Update coordinates display
coordinatesDisplay.textContent =
`X: ${Math.round(x)}, Y: ${Math.round(y)} | ` +
`Norm: ${normalizedX.toFixed(2)}, ${normalizedY.toFixed(2)} | ` +
`%: ${percentX.toFixed(0)}%, ${percentY.toFixed(0)}%`;
});
// Override console.log to capture QuadTap logs
const originalConsoleLog = console.log;
console.log = function() {
// Call original console.log
originalConsoleLog.apply(console, arguments);
// Check if this is a QuadTap log
if (arguments.length > 0 && typeof arguments[0] === 'string' &&
(arguments[0].includes('[QuadTap]') ||
arguments[0].includes('[VideoPlayerAdapter]') ||
arguments[0].includes('[Coordinates]'))) {
// Add to log container
addLogEntry('info', arguments[0], arguments[1] || '');
}
};
// Add log entry
function addLogEntry(type, message, data) {
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
const time = new Date().toLocaleTimeString();
const logTime = document.createElement('span');
logTime.className = 'log-time';
logTime.textContent = `[${time}] `;
const logType = document.createElement('span');
logType.className = `log-type ${type}`;
logType.textContent = type.toUpperCase();
const logMessage = document.createElement('span');
logMessage.className = 'log-message';
logMessage.textContent = ` ${message}`;
logEntry.appendChild(logTime);
logEntry.appendChild(logType);
logEntry.appendChild(logMessage);
if (data) {
const logData = document.createElement('div');
logData.className = 'log-data';
logData.textContent = typeof data === 'object' ? JSON.stringify(data, null, 2) : data;
logEntry.appendChild(logData);
}
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
// Update storage viewer
function updateStorageViewer() {
storageContainer.innerHTML = '';
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
// Only show QuadTap storage items
if (key.startsWith('quadTap_')) {
const value = localStorage.getItem(key);
const storageItem = document.createElement('div');
storageItem.className = 'storage-item';
const storageKey = document.createElement('span');
storageKey.className = 'storage-key';
storageKey.textContent = key;
const storageValue = document.createElement('span');
storageValue.className = 'storage-value';
storageValue.textContent = `: ${value}`;
storageItem.appendChild(storageKey);
storageItem.appendChild(storageValue);
storageContainer.appendChild(storageItem);
}
}
}
// Update video state display
function updateVideoStateDisplay() {
const isPlaying = !videoElement.paused;
videoPlayingElement.textContent = isPlaying ? 'true' : 'false';
videoPlayingElement.className = `state-value ${isPlaying ? 'playing' : 'paused'}`;
videoCurrentTimeElement.textContent = videoElement.currentTime.toFixed(2);
videoDurationElement.textContent = videoElement.duration.toFixed(2);
videoVolumeElement.textContent = videoElement.volume.toFixed(2);
videoMutedElement.textContent = videoElement.muted ? 'true' : 'false';
videoPlaybackRateElement.textContent = videoElement.playbackRate.toFixed(2);
}
// Add video event listeners
videoElement.addEventListener('play', updateVideoStateDisplay);
videoElement.addEventListener('pause', updateVideoStateDisplay);
videoElement.addEventListener('timeupdate', updateVideoStateDisplay);
videoElement.addEventListener('volumechange', updateVideoStateDisplay);
videoElement.addEventListener('ratechange', updateVideoStateDisplay);
// Initialize video state display
videoElement.addEventListener('loadedmetadata', updateVideoStateDisplay);
// Initialize QuadTap
let quadTap = null;
initBtn.addEventListener('click', function() {
if (!quadTap) {
addLogEntry('info', 'Initializing QuadTap with VideoPlayerAdapter...');
// Check if QuadTap is available
if (typeof QuadTap === 'undefined') {
addLogEntry('error', 'QuadTap is not defined. Make sure the bundle is loaded.');
return;
}
try {
// Check if VideoPlayerAdapter is available
if (typeof QuadTap.VideoPlayerAdapter === 'undefined') {
addLogEntry('error', 'VideoPlayerAdapter is not defined. Make sure the bundle is loaded correctly.');
return;
}
// Create a video player adapter
const videoAdapter = QuadTap.VideoPlayerAdapter.forHtml5Video(videoElement, true);
addLogEntry('success', 'Created VideoPlayerAdapter for HTML5 video');
// Check if SettingsBuilder is available
if (typeof QuadTap.SettingsBuilder === 'undefined') {
addLogEntry('error', 'SettingsBuilder is not defined. Make sure the bundle is loaded correctly.');
return;
}
// Build settings with fluent interface
const settingsBuilder = new QuadTap.SettingsBuilder()
.withContainer('main-video-container')
.withVideoSelector('#main-video')
.withDebug(true)
.withAutoCancelTimeout(5000)
.withVideoPlayerApi({
enabled: true,
playMethod: () => videoAdapter.play(),
pauseMethod: () => videoAdapter.pause(),
seekMethod: (time) => videoAdapter.seek(time),
getCurrentTimeMethod: () => videoAdapter.getCurrentTime(),
getDurationMethod: () => videoAdapter.getDuration(),
getVideoIdMethod: () => videoAdapter.getVideoId()
})
.withCoordinateSystem({
type: 'normalized', // 'normalized', 'percentage', or 'absolute'
storeMetadata: true // Include container dimensions in stored data
})
.withEmojiSizes({
default: '24px',
active: '36px'
})
.onOverlayActivate((coordinates) => {
addLogEntry('success', 'Overlay activated', coordinates);
// Video should continue playing when overlay is activated
addLogEntry('info', 'Video should continue playing when overlay is activated');
})
.onThrowDownInitiate((quadrant, coordinates) => {
addLogEntry('success', 'Throw-down initiated', { quadrant, coordinates });
})
.onThrowDownConfirm((quadrant, coordinates, videoInfo) => {
addLogEntry('success', 'Throw-down confirmed', { quadrant, coordinates, videoInfo });
updateStorageViewer();
})
.onThrowDownCancel((quadrant) => {
addLogEntry('warn', 'Throw-down canceled', { quadrant });
})
.onVideoControl((action, currentTime) => {
addLogEntry('info', 'Video control action', { action, currentTime });
});
// Build the settings
const settings = settingsBuilder.build();
addLogEntry('info', 'Built settings with SettingsBuilder');
// Initialize QuadTap with the settings
quadTap = new QuadTap(settings);
addLogEntry('success', 'QuadTap initialized with VideoPlayerAdapter');
} catch (error) {
addLogEntry('error', 'Error initializing QuadTap: ' + error.message);
console.error(error);
}
} else {
addLogEntry('warn', 'QuadTap is already initialized');
}
});
// Destroy QuadTap
destroyBtn.addEventListener('click', function() {
if (quadTap) {
quadTap.destroy();
quadTap = null;
addLogEntry('info', 'QuadTap destroyed');
} else {
addLogEntry('warn', 'QuadTap is not initialized');
}
});
// Clear log
clearLogBtn.addEventListener('click', function() {
logContainer.innerHTML = '';
addLogEntry('info', 'Log cleared');
});
// Clear storage
clearStorageBtn.addEventListener('click', function() {
// Clear only QuadTap storage items
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('quadTap_')) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => {
localStorage.removeItem(key);
});
updateStorageViewer();
addLogEntry('info', 'Storage cleared');
});
// Initialize storage viewer
updateStorageViewer();
// Add initial log entry
addLogEntry('info', 'Video API test page loaded');
addLogEntry('info', 'This test demonstrates the integration with video player APIs');
addLogEntry('info', 'The video should ONLY pause when the lightbox is open, not when the overlay appears');
// Check if QuadTap is available
if (typeof QuadTap === 'undefined') {
addLogEntry('error', 'QuadTap is not defined. Make sure the bundle is loaded.');
} else {
addLogEntry('success', 'QuadTap is available');
// Check if VideoPlayerAdapter is available
if (typeof QuadTap.VideoPlayerAdapter === 'undefined') {
addLogEntry('error', 'VideoPlayerAdapter is not defined. Make sure the bundle is loaded correctly.');
} else {
addLogEntry('success', 'VideoPlayerAdapter is available');
}
// Check if SettingsBuilder is available
if (typeof QuadTap.SettingsBuilder === 'undefined') {
addLogEntry('error', 'SettingsBuilder is not defined. Make sure the bundle is loaded correctly.');
} else {
addLogEntry('success', 'SettingsBuilder is available');
}
// Check if Coordinates is available
if (typeof QuadTap.Coordinates === 'undefined') {
addLogEntry('error', 'Coordinates is not defined. Make sure the bundle is loaded correctly.');
} else {
addLogEntry('success', 'Coordinates is available');
}
}
});</script></body></html>