Jw Player Codepen Direct
.event-log background: #03070c; border-radius: 1rem; padding: 0.8rem 1rem; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.75rem; color: #9cd9ff; max-height: 140px; overflow-y: auto; border: 1px solid #1f2f3a;
.jw-btn.primary background: #0066cc; color: white; box-shadow: 0 2px 6px rgba(0,102,204,0.3);
.slider-container display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem;
<!-- Event Monitor --> <div class="event-log"> <div class="log-header"> <span>๐ก PLAYER EVENTS (real-time)</span> <button id="clearLogBtn" class="clear-log">Clear log</button> </div> <div id="logMessages"> <p>โก Initializing JW Player...</p> </div> </div> <footer> ๐ฅ JW Player demo | HLS streaming + MP4 fallback | Captions & multi-quality | Built-in playlist navigation </footer> </div> jw player codepen
.jw-btn background: #1e2a36; border: none; padding: 0.6rem 1.2rem; border-radius: 2rem; font-weight: 500; font-size: 0.85rem; color: #eef4ff; cursor: pointer; transition: 0.2s; display: inline-flex; align-items: center; gap: 0.4rem; font-family: inherit; box-shadow: 0 1px 2px rgba(0,0,0,0.2);
body background: linear-gradient(145deg, #10151f 0%, #0a0d14 100%); font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 2rem 1.5rem;
input[type="range"]::-webkit-slider-thumb -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%; background: #00a3ff; cursor: pointer; box-shadow: 0 0 4px white; // Captions track is embedded in the HLS
<script> (function() { // Container element const container = document.getElementById('jwplayer-container'); // --------------------------- // Media Playlist Definition // We'll use a diverse set: Big Buck Bunny (with multiple qualities via HLS manifest) // For true demonstration, we include an HLS stream that provides multiple renditions // plus a fallback MP4 to illustrate robustness. Additionally, we add second item. // The JW Player will automatically use the HLS stream to expose quality levels. // Captions track is embedded in the HLS or separately via tracks array. // Playlist items: // Item 1: Big Buck Bunny with HLS (provides adaptive bitrate & captions sample) // Item 2: Sintel trailer (MP4 + separate quality selection example) // Using publicly available test streams that support CORS. const playlist = [ title: "Big Buck Bunny (HLS + Captions)", description: "Adaptive streaming with multiple qualities & subtitles", image: "https://cdn.jwplayer.com/thumbs/abc123-1280.jpg", // placeholder thumb (valid fallback) sources: [ file: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8", // public HLS stream with multiple bitrates label: "HLS (adaptive)", type: "hls", default: true , file: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", label: "MP4 (Fallback)", type: "mp4" ], tracks: [ file: "https://raw.githubusercontent.com/jwplayer/jwplayer-samples/master/captions/big_buck_bunny_en.vtt", label: "English", kind: "captions", "default": true , file: "https://raw.githubusercontent.com/jwplayer/jwplayer-samples/master/captions/big_buck_bunny_es.vtt", label: "Spanish", kind: "captions" ] , title: "Sintel - Epic Fantasy (MP4 + quality levels mock)", description: "Cinematic short with manual quality selection simulation", image: "https://cdn.jwplayer.com/v2/media/8RqDqUzU/poster.jpg?width=1280", sources: [ file: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4", label: "1080p MP4", type: "mp4", default: true ], // For Sintel we manually create quality levels via jwplayer's setCurrentQuality after load? but we'll use API demonstration. // For the sake of quality selection, we'll show how to retrieve qualities from HLS only. tracks: [] ]; // Initialize player with extended features: skin, logo, captions, playback rate controls let playerInstance = null; // Helper to add log entry function addLog(message, type = "info") const logDiv = document.getElementById('logMessages'); const time = new Date().toLocaleTimeString('en-US', hour: '2-digit', minute:'2-digit', second:'2-digit' ); const p = document.createElement('p'); p.innerHTML = `[$time] $message`; logDiv.appendChild(p); p.scrollIntoView( behavior: 'smooth', block: 'nearest' ); // Limit log lines to keep performance while(logDiv.children.length > 35) logDiv.removeChild(logDiv.firstChild); // Setup JW Player function initPlayer() { // Ensure any previous player destroyed if (playerInstance && typeof playerInstance.destroy === 'function') playerInstance.destroy(); // Setup JW Player config const config = playlist: playlist, width: "100%", height: "100%", aspectratio: "16:9", primary: "html5", autostart: false, controls: true, // Custom skin (dark sleek) skin: name: "seven", active: "#00a3ff", background: "#0a0e14", controlbar: background: "rgba(0,0,0,0.8)" , // Logo configuration (custom brand) logo: file: "https://static.jwplayer.com/libraries/logo_jw.png", link: "https://www.jwplayer.com", position: "top-right", hide: false, margin: 10 , // Enable captions menu captions: color: "#FFF", fontSize: 18, backgroundOpacity: 0.7 , // Allow quality selection (for HLS, displays all renditions) // JW Player automatically provides a quality selector if HLS with multiple bitrates // We also manually add the quality menu via config // Plus playback rate controls playbackRateControls: [0.75, 1.0, 1.25, 1.5, 2.0], // Shuffle off, repeat off repeat: false, preload: "auto" ; playerInstance = jwplayer("jwplayer-container").setup(config); // Event listeners for logging and UI updates playerInstance.on('ready', function() { addLog("โ Player ready | Playlist loaded with 2 items"); updateSeekSliderMax(); // Update volume UI playerInstance.getVolume().then(vol => const volPercent = vol * 100; document.getElementById('volumeSlider').value = volPercent; document.getElementById('volumeValue').innerText = Math.round(volPercent) + '%'; ).catch(()=>{}); }); playerInstance.on('play', function() addLog("โถ๏ธ Playback started"); ); playerInstance.on('pause', function() addLog("โธ๏ธ Playback paused"); ); playerInstance.on('complete', function() addLog("๐ Media completed, ready for next item"); ); playerInstance.on('error', function(e) addLog(`โ ๏ธ Error: $e.message `, "err"); ); playerInstance.on('buffer', function() addLog("โณ Buffering..."); ); playerInstance.on('time', function(event) // update seek slider dynamically const seekSlider = document.getElementById('seekSlider'); if (seekSlider && !seekSlider._isUpdating) const duration = playerInstance.getDuration(); if (duration && duration > 0) const percent = (event.position / duration) * 100; seekSlider.value = percent; document.getElementById('seekValue').innerText = Math.floor(event.position) + "s"; ); playerInstance.on('volume', function(event) const vol = event.volume; const volPercent = Math.round(vol * 100); document.getElementById('volumeSlider').value = volPercent; document.getElementById('volumeValue').innerText = volPercent + '%'; ); playerInstance.on('levelsChanged', function() { addLog("๐๏ธ Quality levels updated (adaptive streaming)"); // Optionally list available levels playerInstance.getQualityLevels().then(levels => if(levels && levels.length) addLog(`๐ Available qualities: $levels.map(l => l.label `); ).catch(()=>{}); }); playerInstance.on('playlistItem', function(item) duration: $Math.floor(item.duration)s`); updateSeekSliderMax(); ); } function updateSeekSliderMax() setTimeout(() => const duration = playerInstance.getDuration(); if (duration && isFinite(duration)) const seekSlider = document.getElementById('seekSlider'); seekSlider.max = 100; // No further changes needed , 200); // Helper to apply seek based on percentage function seekToPercentage(percent) const duration = playerInstance.getDuration(); if (duration && duration > 0) const seekTime = (percent / 100) * duration; playerInstance.seek(seekTime); addLog(`โฉ Seek to $Math.floor(seekTime)s ($Math.floor(percent)%)`); // Volume slider handler function setupUIInteractions() const playBtn = document.getElementById('playBtn'); const pauseBtn = document.getElementById('pauseBtn'); const stopBtn = document.getElementById('stopBtn'); const muteBtn = document.getElementById('muteBtn'); const unmuteBtn = document.getElementById('unmuteBtn'); const nextBtn = document.getElementById('nextBtn'); const volumeSlider = document.getElementById('volumeSlider'); const seekSlider = document.getElementById('seekSlider'); const volumeVal = document.getElementById('volumeValue'); const seekVal = document.getElementById('seekValue'); const qualitySelect = document.getElementById('qualitySelect'); const applyQualityBtn = document.getElementById('applyQualityBtn'); const clearLogBtn = document.getElementById('clearLogBtn'); playBtn.addEventListener('click', () => if(playerInstance) playerInstance.play(true); addLog("๐ฌ Play command via API"); ); pauseBtn.addEventListener('click', () => if(playerInstance) playerInstance.pause(true); addLog("โธ๏ธ Pause command via API"); ); stopBtn.addEventListener('click', () => if(playerInstance) playerInstance.stop(); addLog("โน๏ธ Stop command (resets to beginning)"); ); muteBtn.addEventListener('click', () => if(playerInstance) playerInstance.setMute(true); addLog("๐ Muted"); ); unmuteBtn.addEventListener('click', () => if(playerInstance) playerInstance.setMute(false); addLog("๐ Unmuted"); ); nextBtn.addEventListener('click', () => if(playerInstance) playerInstance.playlistNext(); addLog("โฉ Skipped to next playlist item"); ); volumeSlider.addEventListener('input', (e) => const val = parseInt(e.target.value, 10); volumeVal.innerText = val + '%'; const volumeFloat = val / 100; if(playerInstance) playerInstance.setVolume(volumeFloat); ); seekSlider.addEventListener('input', (e) => ); seekSlider.addEventListener('change', (e) => const percent = parseFloat(e.target.value); seekToPercentage(percent); setTimeout(() => seekSlider._isUpdating = false; , 100); ); // Quality selection: Since HLS stream provides multiple renditions, we use setCurrentQuality applyQualityBtn.addEventListener('click', async () => const selected = qualitySelect.value; if (!playerInstance) return; if (selected === 'auto') // set to auto (adaptive) playerInstance.setCurrentQuality(-1); addLog("๐๏ธ Quality set to auto (adaptive)"); return; // Get quality levels and match by label or height try levels.length === 0) addLog("โ ๏ธ No quality levels available (current stream may not support ABR)"); return; let targetLevel = null; const targetNum = parseInt(selected, 10); for(let i = 0; i < levels.length; i++) (lvl.label && lvl.label.includes(selected))) targetLevel = i; break; // fallback if label contains e.g. '720' if(lvl.label && lvl.label.includes(selected)) targetLevel = i; if(targetLevel !== null) await playerInstance.setCurrentQuality(targetLevel); addLog(`โจ Quality switched to $selectedp (index $targetLevel)`); else addLog(`โ Quality $selectedp not found. Available: $levels.map(l => l.height `); catch(e) addLog(`Failed to set quality: $e.message`); ); clearLogBtn.addEventListener('click', () => const logDiv = document.getElementById('logMessages'); logDiv.innerHTML = '<p>๐งน Log cleared. New events will appear.</p>'; ); // Also add periodic duration update for seek max when new item loads playerInstance.on('playlistItem', () => setTimeout(() => const dur = playerInstance.getDuration(); if(dur && dur > 0) seekSlider.max = 100; seekSlider.value = 0; seekVal.innerText = "0s"; , 300); ); // Initialize everything initPlayer(); // Small delay to ensure player is in global before UI binding setTimeout(() => if(playerInstance) setupUIInteractions(); else // fallback: poll for player ready const interval = setInterval(() => if(playerInstance) clearInterval(interval); setupUIInteractions(); , 100); , 400); // Expose for debugging (optional) window.debugPlayer = () => playerInstance; })(); </script> </body> </html>
.log-header display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 0.5rem;
#jwplayer-container width: 100%; height: 100%; background-color: #000; but we'll use API demonstration
@media (max-width: 680px) .demo-container padding: 1rem; .jw-btn padding: 0.4rem 1rem; font-size: 0.75rem; </style>
footer font-size: 0.7rem; text-align: center; color: #4b637a; margin-top: 1.5rem;
h1 font-size: 1.9rem; font-weight: 600; letter-spacing: -0.3px; background: linear-gradient(135deg, #FFFFFF 30%, #b0c4ff 80%); -webkit-background-clip: text; background-clip: text; color: transparent; margin-bottom: 0.3rem;
<!-- Interactive API Panel --> <div class="control-panel"> <div class="panel-title"> <span>๐ฎ PLAYER CONTROLS (API)</span> </div> <div class="button-group"> <button class="jw-btn" id="playBtn">โถ๏ธ Play</button> <button class="jw-btn" id="pauseBtn">โธ๏ธ Pause</button> <button class="jw-btn" id="stopBtn">โน๏ธ Stop</button> <button class="jw-btn" id="muteBtn">๐ Mute</button> <button class="jw-btn" id="unmuteBtn">๐ Unmute</button> <button class="jw-btn" id="nextBtn">โฉ Next (Playlist)</button> </div> <div class="slider-container"> <span class="slider-label">๐ Volume</span> <input type="range" id="volumeSlider" min="0" max="100" value="80" step="1"> <span class="slider-label" id="volumeValue">80%</span> </div> <div class="slider-container"> <span class="slider-label">โฑ๏ธ Seek (sec)</span> <input type="range" id="seekSlider" min="0" max="100" value="0" step="0.5"> <span class="slider-label" id="seekValue">0s</span> </div> <div class="button-group"> <span style="align-self:center; color:#b8d0e9;">๐๏ธ Quality preset:</span> <select id="qualitySelect" class="quality-select"> <option value="auto">Auto (adaptive)</option> <option value="720">720p</option> <option value="480">480p</option> <option value="360">360p</option> </select> <button class="jw-btn" id="applyQualityBtn">Apply Quality</button> </div> </div>
.panel-title display: flex; align-items: center; gap: 0.5rem; color: #d6e6ff; font-weight: 500; margin-bottom: 1rem; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 1px;