Wavesurfer voor audio file player

This commit is contained in:
NH Gooi committed 2024-09-25 17:30:15 +02:00
1 parent 1dadbc62af
commit 6c732f90e8
7 files changed
+137 -68

No files matched your search

+1 -1
View File
@@ -95,7 +95,7 @@ class RadioController extends Controller
{
$programs = [];
$now = new \DateTimeImmutable('2 minutes ago');
$page = (int)$request->get('pagina', 1);
$page = (int)$request->get('pagina', 1);
$apiResult = $this->API('programma/schema/recent?pagina=' . (int)max(1, $page) . '&aantal=12');
foreach($apiResult->schedule as $item) {
if(!$item->program->nonstop && !$item->program->rerun) {
+13 -2
View File
@@ -14,6 +14,8 @@ class StreamController extends Controller
return view('listen', [
'source' => self::$STREAM_URL . 'mp3live',
'title' => 'Luister live',
'lengte' => 0,
'waveform' => null,
'content' => 'de live-uitzending van NH Gooi.',
'isStream' => true ]);
}
@@ -53,6 +55,8 @@ class StreamController extends Controller
'source' => $this->API_URL . 'podcast/stream/' . $apiResult->url,
'title' => $podcast->title,
'content' => $podcast->title,
'lengte' => $podcast->duration / 1000,
'waveform' => $podcast->waveform,
'isStream' => false,
'canDownload' => $this->API_URL . 'podcast/download/' . $apiResult->url ]);
}
@@ -61,6 +65,11 @@ class StreamController extends Controller
$date = (new \DateTimeImmutable())->setDate($year, $month, $day)->setTime($hour, 0, 0);
$current = $date->add(\DateInterval::createFromDateString($offset . ' hours'));
$programma = $this->API("programma/details/" . $current->Format("Y/m/d/H"));
if(!$programma->is_beschikbaar) {
return view('listen', ['notAvailable' => true]);
}
$hours = [];
for($i = 0; $i < $duration; $i++) {
$other = $date->add(\DateInterval::createFromDateString($i . ' hours'));
@@ -69,10 +78,12 @@ class StreamController extends Controller
}
return view('listen', [
'source' => $this->API_URL . 'programma/stream/' . $current->format('Y/m/d/H') . '/1',
'source' => $this->API_URL . 'programma/stream/' . $current->format('Y/m/d/H'),
'tabs' => $hours,
'title' => 'Uitzending terugluisteren',
'content' => 'de uitzending van ' . $current->format('d-m-Y, H') . ':00 uur',
'lengte' => $programma->waveform->length,
'waveform' => $programma->waveform,
'content' => $programma->programma->name . ' van ' . $current->format('d-m-Y, H') . ':00 uur',
'isStream' => false,
'canDownload' => false ]);
}
+1
View File
@@ -0,0 +1 @@
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).WaveSurfer=e()}(this,(function(){"use strict";function t(t,e,i,n){return new(i||(i=Promise))((function(s,r){function o(t){try{h(n.next(t))}catch(t){r(t)}}function a(t){try{h(n.throw(t))}catch(t){r(t)}}function h(t){var e;t.done?s(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(o,a)}h((n=n.apply(t,e||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;class e{constructor(){this.listeners={}}on(t,e,i){if(this.listeners[t]||(this.listeners[t]=new Set),this.listeners[t].add(e),null==i?void 0:i.once){const i=()=>{this.un(t,i),this.un(t,e)};return this.on(t,i),i}return()=>this.un(t,e)}un(t,e){var i;null===(i=this.listeners[t])||void 0===i||i.delete(e)}once(t,e){return this.on(t,e,{once:!0})}unAll(){this.listeners={}}emit(t,...e){this.listeners[t]&&this.listeners[t].forEach((t=>t(...e)))}}const i={decode:function(e,i){return t(this,void 0,void 0,(function*(){const t=new AudioContext({sampleRate:i});return t.decodeAudioData(e).finally((()=>t.close()))}))},createBuffer:function(t,e){return"number"==typeof t[0]&&(t=[t]),function(t){const e=t[0];if(e.some((t=>t>1||t<-1))){const i=e.length;let n=0;for(let t=0;t<i;t++){const i=Math.abs(e[t]);i>n&&(n=i)}for(const e of t)for(let t=0;t<i;t++)e[t]/=n}}(t),{duration:e,length:t[0].length,sampleRate:t[0].length/e,numberOfChannels:t.length,getChannelData:e=>null==t?void 0:t[e],copyFromChannel:AudioBuffer.prototype.copyFromChannel,copyToChannel:AudioBuffer.prototype.copyToChannel}}};function n(t,e){const i=e.xmlns?document.createElementNS(e.xmlns,t):document.createElement(t);for(const[t,s]of Object.entries(e))if("children"===t)for(const[t,s]of Object.entries(e))"string"==typeof s?i.appendChild(document.createTextNode(s)):i.appendChild(n(t,s));else"style"===t?Object.assign(i.style,s):"textContent"===t?i.textContent=s:i.setAttribute(t,s.toString());return i}function s(t,e,i){const s=n(t,e||{});return null==i||i.appendChild(s),s}var r=Object.freeze({__proto__:null,createElement:s,default:s});const o={fetchBlob:function(e,i,n){return t(this,void 0,void 0,(function*(){const s=yield fetch(e,n);if(s.status>=400)throw new Error(`Failed to fetch ${e}: ${s.status} (${s.statusText})`);return function(e,i){t(this,void 0,void 0,(function*(){if(!e.body||!e.headers)return;const n=e.body.getReader(),s=Number(e.headers.get("Content-Length"))||0;let r=0;const o=e=>t(this,void 0,void 0,(function*(){r+=(null==e?void 0:e.length)||0;const t=Math.round(r/s*100);i(t)})),a=()=>t(this,void 0,void 0,(function*(){let t;try{t=yield n.read()}catch(t){return}t.done||(o(t.value),yield a())}));a()}))}(s.clone(),i),s.blob()}))}};class a extends e{constructor(t){super(),this.isExternalMedia=!1,t.media?(this.media=t.media,this.isExternalMedia=!0):this.media=document.createElement("audio"),t.mediaControls&&(this.media.controls=!0),t.autoplay&&(this.media.autoplay=!0),null!=t.playbackRate&&this.onMediaEvent("canplay",(()=>{null!=t.playbackRate&&(this.media.playbackRate=t.playbackRate)}),{once:!0})}onMediaEvent(t,e,i){return this.media.addEventListener(t,e,i),()=>this.media.removeEventListener(t,e,i)}getSrc(){return this.media.currentSrc||this.media.src||""}revokeSrc(){const t=this.getSrc();t.startsWith("blob:")&&URL.revokeObjectURL(t)}canPlayType(t){return""!==this.media.canPlayType(t)}setSrc(t,e){if(this.getSrc()===t)return;this.revokeSrc();const i=e instanceof Blob&&this.canPlayType(e.type)?URL.createObjectURL(e):t;this.media.src=i}destroy(){this.media.pause(),this.isExternalMedia||(this.media.remove(),this.revokeSrc(),this.media.src="",this.media.load())}setMediaElement(t){this.media=t}play(){return t(this,void 0,void 0,(function*(){return this.media.play()}))}pause(){this.media.pause()}isPlaying(){return!this.media.paused&&!this.media.ended}setTime(t){this.media.currentTime=t}getDuration(){return this.media.duration}getCurrentTime(){return this.media.currentTime}getVolume(){return this.media.volume}setVolume(t){this.media.volume=t}getMuted(){return this.media.muted}setMuted(t){this.media.muted=t}getPlaybackRate(){return this.media.playbackRate}isSeeking(){return this.media.seeking}setPlaybackRate(t,e){null!=e&&(this.media.preservesPitch=e),this.media.playbackRate=t}getMediaElement(){return this.media}setSinkId(t){return this.media.setSinkId(t)}}class h extends e{constructor(t,e){super(),this.timeouts=[],this.isScrollable=!1,this.audioData=null,this.resizeObserver=null,this.lastContainerWidth=0,this.isDragging=!1,this.subscriptions=[],this.subscriptions=[],this.options=t;const i=this.parentFromOptionsContainer(t.container);this.parent=i;const[n,s]=this.initHtml();i.appendChild(n),this.container=n,this.scrollContainer=s.querySelector(".scroll"),this.wrapper=s.querySelector(".wrapper"),this.canvasWrapper=s.querySelector(".canvases"),this.progressWrapper=s.querySelector(".progress"),this.cursLine truncated
+1 -1
View File
@@ -203,7 +203,7 @@
function openPlayerInNewScreen() {
$(".player").click(function (e) {
e.preventDefault();
window.open($(this).attr('href'), '_player', 'width=550,height=500,titlebar,close');
window.open($(this).attr('href'), '_player', 'width=550,height=600,titlebar,close');
return false;
});
}
+6 -5
View File
@@ -4,8 +4,8 @@
<a href="javascript:window.close();" class="close btn"><span class='fa fa-times fa-fw'></span> Venster sluiten</a>
<p class="logo"><a href="{{ url('/') }}"><img src="{{ url( 'images/logo-NHGooi.svg' )}}"></a></p>
@if(false && $isStream)
<p>Wegens een technisch probleem is NH Gooi momenteel niet via Internet te beluisteren. Onze excuses voor het
@if(isset($notAvailable) && $notAvailable)
<p>Helaas is de door u gekozen audio momenteel niet via Internet te beluisteren. Onze excuses voor het
ongemak.</p>
<p>In Hilversum, Huizen en de BEL-gemeenten zijn wij te ontvangen op 92.0 FM of 105.1 FM.</p>
@else
@@ -41,9 +41,10 @@
</audio> --}}
@include('widgets.audioplayer', [
'source' =>str_replace("stream", "download", $source),
'lengte' => 0,
'waveform' => []
'source' => $source,
'isStream' => $isStream,
'lengte' => $lengte,
'waveform' => $waveform
])
</p>
+2 -1
View File
@@ -113,6 +113,7 @@
<div class="announcement">
@include("widgets.audioplayer", [
'isStream' => false,
'source' => $streamUrl,
'lengte' => $podcast->duration / 1000,
'waveform' => $podcast->waveform
@@ -122,7 +123,7 @@
<a class="action_button btn" href="{{$audioUrl}}" title="Download dit fragment als MP3">
<span>Download fragment</span>
</a>
<a class="action_button btn player" href="{{$popoutUrl}}">
<a class="action_button btn player" href="{{$popoutUrl}}" onclick="pause()">
<span>Luister in nieuw venster</span>
</a>
</div>
+113 -58
View File
@@ -1,31 +1,23 @@
<?php $id = uniqid('player_'); ?>
<div class="audioplayer" id="{{ $id }}">
{{--
<label for="lengte" class="col-12 col-sm-2 col-form-label">Lengte</label>
<div class="col-12 col-sm-10 my-auto">
@if( $lengte > 60)
{{ floor($lengte / 60) }} minuten en {{ floor(($lengte % 60)) }} seconden
@else
{{ $lengte }} seconden
@endif
</div>
--}}
@if(!$isStream)
<div class="waveform">
<div class="time">0:00</div>
<div class="duration">0:00</div>
<div class="hover"></div>
</div>
@endif
<div class="volume-controls">
<button class="btn-toggle-mute" type="button">
<button class="btn-toggle-mute" type="button" onclick="toggleMute()">
<span class="fa fa-volume-high"></span>
</button>
<input class="volume-slider" type="range" min="0" max="1" step="0.01" value="1"
onchange="wavesurfer_{{ $id }}.setVolume( this.value )" />
onchange="setVolume( this.value )" />
</div>
<div class="audio-controls">
@if(!$isStream)
<button class="btn btn-jump" type="button" onclick="wavesurfer_{{ $id }}.skip(-60)">
<span class="fa fa-backward-fast"></span>
<label>-60 s</label>
@@ -34,10 +26,12 @@
<span class="fa fa-backward-step"></span>
<label>-10 s</label>
</button>
<button class="btn btn-play" type="button" onclick="wavesurfer_{{ $id }}.playPause()">
@endif
<button class="btn btn-play" type="button" onclick="playPause()">
<span class="play-button-icon fa fa-play"></span>
<label class="play-button-label">Afspelen</label>
</button>
@if(!$isStream)
<button class="btn btn-jump" type="button" onclick="wavesurfer_{{ $id }}.skip(10)">
<span class="fa fa-forward-step"></span>
<label>+10 s</label>
@@ -46,11 +40,80 @@
<span class="fa fa-forward-fast"></span>
<label>+60 s</label>
</button>
@endif
</div>
</div>
@if($isStream)
<audio id="audio_{{ $id }}">
<source src="{{ $source }}" type="audio/mp3" />
</audio>
<script>
var player_{{ $id }};
setVolume = volume => player_{{ $id }}.volume = volume;
function toggleMute () {
var isMuted = !player_{{ $id }}.muted;
player_{{ $id }}.muted = isMuted;
if(isMuted) {
$('#{{ $id }} .btn-toggle-mute').html('<span class="fa fa-volume-xmark"></span>');
$('#{{ $id }} .volume-slider').attr('disabled', 'disabled');
} else {
$('#{{ $id }} .btn-toggle-mute').html('<span class="fa fa-volume-high"></span>');
$('#{{ $id }} .volume-slider').removeAttr('disabled');
}
}
pause = () => player_{{ $id }}.pause();
function playPause() {
var player = player_{{ $id }};
if (player.paused) {
player.play();
} else {
player.pause();
}
}
addEventListener("DOMContentLoaded", function() {
var player = document.getElementById( "audio_{{ $id }}");
player_{{ $id }} = player;
$(player_{{ $id }}).on('play', () => {
$('#{{ $id }} .play-button-icon').addClass('fa-pause').removeClass('fa-play');
$('#{{ $id }} .play-button-label').text('Pauzeren');
})
$(player_{{ $id }}).on('pause', () => {
$('#{{ $id }} .play-button-icon').addClass('fa-play').removeClass('fa-pause');
$('#{{ $id }} .play-button-label').text('Verder spelen');
});
});
</script>
@else
<script>
var wavesurfer_{{ $id }};
setVolume = volume => wavesurfer_{{ $id }}.setVolume( volume );
playPause = () => wavesurfer_{{ $id }}.playPause();
pause = () => wavesurfer_{{ $id }}.pause();
function toggleMute () {
var isMuted = !wavesurfer_{{ $id }}.getMuted();
wavesurfer_{{ $id }}.setMuted(isMuted);
if(isMuted) {
$('#{{ $id }} .btn-toggle-mute').html('<span class="fa fa-volume-xmark"></span>');
$('#{{ $id }} .volume-slider').attr('disabled', 'disabled');
} else {
$('#{{ $id }} .btn-toggle-mute').html('<span class="fa fa-volume-high"></span>');
$('#{{ $id }} .volume-slider').removeAttr('disabled');
}
}
addEventListener("DOMContentLoaded", function() {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
@@ -60,12 +123,15 @@
container: '#{{ $id }} .waveform',
waveColor: '#3A96EE',
progressColor: '#0118A1',
height: 50,
barWidth: 1,
// duration: {{ $lengte }},
// @if($waveform)
// peaks: {{ json_encode($waveform->data) }},
// normalize: true,
// @endif
@if(isset($lengte))
duration: {{ $lengte }},
@endif
@if($waveform)
peaks: {{ json_encode($waveform->data) }},
normalize: true,
@endif
url: '{{ $source }}',
});
@@ -83,17 +149,6 @@
$('#{{ $id }} .play-button-label').text('Verder spelen');
})
$('#{{ $id }} .btn-toggle-mute').on('click', () => {
var isMuted = !wavesurfer_{{ $id }}.getMuted();
wavesurfer_{{ $id }}.setMuted(isMuted);
if(isMuted) {
$('#{{ $id }} .btn-toggle-mute').html('<span class="fa fa-volume-xmark"></span>');
$('#{{ $id }} .volume-slider').attr('disabled', 'disabled');
} else {
$('#{{ $id }} .btn-toggle-mute').html('<span class="fa fa-volume-high"></span>');
$('#{{ $id }} .volume-slider').removeAttr('disabled');
}
});
// Hover effect
{
const hover = document.querySelector('#{{ $id }} .hover')
@@ -103,28 +158,30 @@
// Current time & duration
{
const formatTime = (seconds) => {
const minutes = Math.floor(seconds / 60)
const secondsRemainder = Math.round(seconds) % 60
const paddedSeconds = `0${secondsRemainder}`.slice(-2)
return `${minutes}:${paddedSeconds}`
}
const formatTime = (seconds) => {
const minutes = Math.floor(seconds / 60)
const secondsRemainder = Math.round(seconds) % 60
const paddedSeconds = `0${secondsRemainder}`.slice(-2)
return `${minutes}:${paddedSeconds}`
}
const timeEl = document.querySelector('#{{ $id }} .time')
const durationEl = document.querySelector('#{{ $id }} .duration')
wavesurfer_{{ $id }}.on('decode', (duration) => (durationEl.textContent = formatTime(duration)))
wavesurfer_{{ $id }}.on('timeupdate', (currentTime) => (timeEl.textContent = formatTime(currentTime)))
const timeEl = document.querySelector('#{{ $id }} .time')
const durationEl = document.querySelector('#{{ $id }} .duration')
wavesurfer_{{ $id }}.on('decode', (duration) => (durationEl.textContent = formatTime(duration)))
wavesurfer_{{ $id }}.on('timeupdate', (currentTime) => (timeEl.textContent = formatTime(currentTime)))
}
});
</script>
@endif
<style>
#{{ $id }} .waveform {
.audioplayer .waveform {
cursor: pointer;
position: relative;
}
#{{ $id }} .hover {
.audioplayer .hover {
position: absolute;
left: 0;
top: 0;
@@ -138,12 +195,12 @@
transition: opacity 0.2s ease;
}
#{{ $id }} .waveform:hover .hover {
.audioplayer .waveform:hover .hover {
opacity: 1;
}
#{{ $id }} .time,
#{{ $id }} .duration {
.audioplayer .time,
.audioplayer .duration {
position: absolute;
z-index: 11;
top: 50%;
@@ -155,21 +212,21 @@
color: #ddd;
}
#{{ $id }} .time {
.audioplayer .time {
left: 0;
}
#{{ $id }} .duration {
.audioplayer .duration {
right: 0;
}
#{{ $id }} .audio-controls {
.audioplayer .audio-controls {
width: 100%;
justify-content: center;
display: flex;
}
#{{ $id }} .audio-controls .btn {
.audioplayer .audio-controls .btn {
padding-left: 0;
padding-right: 0;
overflow: hidden;
@@ -179,35 +236,33 @@
}
@media(max-width:720px) {
#{{ $id }} .audio-controls .btn label {
.audioplayer .audio-controls .btn label {
display: none;
}
}
#{{ $id }} .audio-controls .btn.btn-play {
.audioplayer .audio-controls .btn.btn-play {
flex: 50% 1 0;
}
#{{ $id }} .volume-controls {
.audioplayer .volume-controls {
display: flex;
}
#{{ $id }} .volume-controls .volume-slider
{
.audioplayer .volume-controls .volume-slider {
flex: 100% 1 1;
}
#{{ $id }} .audio-controls .btn,
#{{ $id }} .volume-controls .btn-toggle-mute {
.audioplayer .audio-controls .btn,
.audioplayer .volume-controls .btn-toggle-mute {
flex: 100px 1 1;
padding: 10px 0px 10px 0px;
background: #5ba8f4;
border-radius: 5px;
border-color: solid 1px #0f259d;
border-color: solid 1px #0f259d;
color: white;
font-family: Nunito, serif;
text-align: center;
text-decoration: none;
}
</style>