switch over to MediaElement.js so we can add skip-30-second buttons

This commit is contained in:
2021-10-18 23:08:58 -07:00
parent cf9988c367
commit 4642c685fa
7 changed files with 156 additions and 70 deletions

3
.gitmodules vendored
View File

@@ -4,3 +4,6 @@
[submodule "getid3"]
path = getid3
url = https://github.com/JamesHeinrich/getID3
[submodule "mediaelement-plugins"]
path = mediaelement-plugins
url = https://github.com/mediaelement/mediaelement-plugins

10
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9000
}
]
}

View File

@@ -7,4 +7,5 @@ ENV CHANNELS "${CHANNELS:-1}"
ENV BITRATE "${BITRATE:-32}"
RUN (cd /var/www/html && curl -L https://github.com/JamesHeinrich/getID3/archive/refs/tags/v1.9.21.tar.gz | tar xzf - && mv getID3-* getid3)
RUN (cd /var/www/html && curl -L https://github.com/mediaelement/mediaelement/archive/refs/tags/5.0.1.tar.gz | tar xzf - && mv mediaelement-* mediaelement)
RUN (cd /var/www/html && curl -L https://github.com/mediaelement/mediaelement-plugins/archive/refs/tags/2.6.1.tar.gz | tar xzf - && mv mediaelement-plugins-* mediaelement-plugins)
VOLUME /var/www/html/

View File

@@ -3,6 +3,8 @@ StreamShifter
Capture streaming audio from your favorite sources to build a custom podcast stream. I used this for a number of years on a bog-standard Linux/Nginx/PHP-FPM server, and have recently containerized it for use with Docker. It'll record live audio captured from a URL, transcoding it into a common format along the way. You can listen to the most recent stream as it records with HTTP live streaming, or you can listen later to the podcast.
StreamShifter actually uses MediaElement.js now...it wasn't previously. Buttons to skip forward and back 30 seconds have been added to the player, which are convenient for skipping past commercials.
Standalone Instructions
-----------------------

141
index.php
View File

@@ -1,78 +1,79 @@
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Live Audio and Podcasts</title>
<meta name="viewport" content="width=320">
<script src="mediaelement/build/jquery.js"></script>
<script src="mediaelement/build/mediaelement-and-player.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<link rel="stylesheet" href="mediaelement/build/mediaelementplayer.min.css" />
<script type="text/javascript">
function LoadAudio(file)
{
document.getElementById("player").style.visibility="visible";
document.getElementById("video").style.visibility="hidden";
<title>Live Audio and Podcasts</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
document.getElementById("player").innerHTML="<audio src=\"audio/"+file+"\" type=\"audio/m4a\" controls=\"controls\" width=\"500px\" />";
document.getElementById("filename").innerHTML=file;
$('audio').mediaelementplayer();
}
function LoadM3U8(file)
{
document.getElementById("player").style.visibility="hidden";
document.getElementById("video").style.visibility="visible";
var video=document.getElementById("video");
if (Hls.isSupported())
{
var hls=new Hls();
hls.loadSource(file);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {video.play();});
}
else if (video.canPlayType("application/vnd.apple.mpegurl"))
{
video.src=file;
video.addEventListener("loadedmetadata", function() {video.play();});
}
document.getElementById("filename").innerHTML=file;
}
</script>
<link rel="stylesheet" href="mediaelement/build/mediaelementplayer.css">
<link rel="stylesheet" href="mediaelement-plugins/dist/jump-forward/jump-forward.css">
<link rel="stylesheet" href="mediaelement-plugins/dist/skip-back/skip-back.css">
</head>
<body>
<h1>Live Audio and Podcasts</h1>
<ul>
<?php
$arr=[];
$c=0;
if ($h=opendir("audio"))
{
while (($e=readdir($h))!==false)
<h1>Live Audio and Podcasts</h1>
<ul>
<?php
$arr=[];
$c=0;
if ($h=opendir("audio"))
{
if (strlen($e)>4)
if (substr_compare($e,".m4a",-4,4)===0)
$arr[$c++]="<li><a href=\"audio/".$e."\">".$e."</a> (<a href=\"#\" onclick=\"LoadAudio('".$e."')\">Load in Player</a>)</li>\n";
while (($e=readdir($h))!==false)
{
if (strlen($e)>4)
if (substr_compare($e,".m4a",-4,4)===0)
$arr[$c++]="<li><a href=\"audio/".$e."\">".$e."</a> (<a href=\"#\" onclick=\"Load('audio/".$e."', 'audio/m4a')\">Load in Player</a>)</li>\n";
}
closedir($h);
}
closedir($h);
}
if ($h=opendir("."))
{
while (($e=readdir($h))!==false)
if ($h=opendir("."))
{
if (strlen($e)>5)
if (substr_compare($e,".m3u8",-5,5)===0)
$arr[$c++]="<li><a href=\"".$e."\">".$e."</a> (<a href=\"#\" onclick=\"LoadM3U8('".$e."')\">Load in Player</a>)</li>\n";
while (($e=readdir($h))!==false)
{
if (strlen($e)>5)
if (substr_compare($e,".m3u8",-5,5)===0)
$arr[$c++]="<li><a href=\"".$e."\">".$e."</a> (<a href=\"#\" onclick=\"Load('".$e."', 'application/x-mpegURL')\">Load in Player</a>)</li>\n";
}
closedir($h);
}
closedir($h);
}
sort($arr);
foreach ($arr as $item)
echo $item;
?>
<li><a href="rss.php">RSS Feed</a></li>
</ul>
<p>
<span id="player" style="visibility: hidden;"></span>
<video id="video" controls autoplay style="width: 500px; height: 50px; visibility: hidden;"></video>
<br /><span id="filename"></span></p>
</body></html>
sort($arr);
foreach ($arr as $item)
echo $item;
?>
<li><a href="rss.php">RSS Feed</a></li>
</ul>
<br />
<div class="media-wrapper">
<audio id="player1" width="400" height="45" controls preload="none"></audio>
</div>
<br /><span id="filename"></span>
<script src="mediaelement/build/mediaelement-and-player.min.js"></script>
<script src="mediaelement-plugins/dist/jump-forward/jump-forward.js"></script>
<script src="mediaelement-plugins/dist/skip-back/skip-back.js"></script>
<script>
var mediaElements = document.querySelectorAll('video, audio');
for (var i = 0, total = mediaElements.length; i < total; i++) {
var features = ['playpause', 'current', 'skipback', 'progress', 'jumpforward', 'duration'];
new MediaElementPlayer(mediaElements[i], {
autoRewind: false,
features: features,
});
}
function Load(src, mime_type)
{
document.getElementById("player1").src=src;
document.getElementById("player1").type=mime_type;
document.getElementById("player1").load();
document.getElementById("player1").play();
document.getElementById("filename").innerHTML=src;
}
</script>
</body>
</html>

1
mediaelement-plugins Submodule

Submodule mediaelement-plugins added at 9651e3f626

68
mejs-controls.svg Normal file
View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol id="icon-captions" viewBox="0 0 18 14" xmlns="http://www.w3.org/2000/svg">
<g fill="#fff">
<path d="M-.67-4.08h14a2,2,0,0,1,2,2v10a2,2,0,0,1-2,2h-14a2,2,0,0,1-2-2v-10A2,2,0,0,1-.67-4.08Z" transform="translate(2.67 4.08)"/>
</g>
<g fill="#000">
<path d="M2.88,6.67a2.81,2.81,0,0,1-2.1-1A3.91,3.91,0,0,1,.88.87c.5-.6,2-1.7,4.6.2l-.6.8c-1.4-1-2.6-1.1-3.3-.3a3,3,0,0,0-.1,3.5c.7.9,1.9.8,3.4-.1l.5.9a5.28,5.28,0,0,1-2.5.8Zm7.5,0a2.81,2.81,0,0,1-2.1-1,3.91,3.91,0,0,1,.1-4.8c.5-.6,2-1.7,4.6.2l-.5.8c-1.4-1-2.6-1.1-3.3-.3a3,3,0,0,0-.1,3.5c.7.9,1.9.8,3.4-.1l.5.9a6.07,6.07,0,0,1-2.6.8Z" transform="translate(2.67 4.08)"/>
</g>
</symbol>
<symbol id="icon-chapters" viewBox="0 0 16.6 13" xmlns="http://www.w3.org/2000/svg"><path d="M1.5,0A1.54,1.54,0,0,1,3,1.5,1.54,1.54,0,0,1,1.5,3,1.54,1.54,0,0,1,0,1.5,1.47,1.47,0,0,1,1.5,0ZM6.6,0h8.5a1.47,1.47,0,0,1,1.5,1.5A1.54,1.54,0,0,1,15.1,3H6.6A1.47,1.47,0,0,1,5.1,1.5,1.37,1.37,0,0,1,6.6,0ZM1.5,5A1.54,1.54,0,0,1,3,6.5,1.54,1.54,0,0,1,1.5,8,1.54,1.54,0,0,1,0,6.5,1.47,1.47,0,0,1,1.5,5ZM6.6,5h8.5a1.47,1.47,0,0,1,1.5,1.5A1.54,1.54,0,0,1,15.1,8H6.6A1.47,1.47,0,0,1,5.1,6.5,1.37,1.37,0,0,1,6.6,5ZM1.5,10a1.5,1.5,0,0,1,0,3A1.54,1.54,0,0,1,0,11.5,1.47,1.47,0,0,1,1.5,10Zm5.1,0h8.5a1.47,1.47,0,0,1,1.5,1.5A1.54,1.54,0,0,1,15.1,13H6.6a1.47,1.47,0,0,1-1.5-1.5A1.37,1.37,0,0,1,6.6,10Z"/></symbol>
<symbol id="icon-fullscreen" viewBox="0 0 17.8 17.8" xmlns="http://www.w3.org/2000/svg"><path d="M0,1A.94.94,0,0,1,1,0H6.4c.6,0,.7.3.3.7l-6,6C.3,7.1,0,7,0,6.4ZM0,16.8a.94.94,0,0,0,1,1H6.4c.6,0,.7-.3.3-.7l-6-6c-.4-.4-.7-.3-.7.3ZM17.8,1a.94.94,0,0,0-1-1H11.4c-.6,0-.7.3-.3.7l6,6c.4.4.7.3.7-.3Zm0,15.8a.94.94,0,0,1-1,1H11.4c-.6,0-.7-.3-.3-.7l6-6c.4-.4.7-.3.7.3Z"/></symbol>
<symbol id="icon-unfullscreen" viewBox="0 0 17.8 17.8" xmlns="http://www.w3.org/2000/svg"><path d="M11.74,4.64a.94.94,0,0,0,1,1h4.1c.6,0,.7-.3.3-.7L12.44.24c-.4-.4-.7-.3-.7.3Zm-7.1,1a.94.94,0,0,0,1-1V.54c0-.6-.3-.7-.7-.3L.24,4.94c-.4.4-.3.7.3.7Zm1,7.1a.94.94,0,0,0-1-1H.54c-.6,0-.7.3-.3.7l4.7,4.7c.4.4.7.3.7-.3Zm7.1-1a.94.94,0,0,0-1,1v4.1c0,.5.3.7.7.3l4.7-4.7c.4-.4.3-.7-.3-.7Z"/></symbol>
<symbol id="icon-mute" viewBox="0 0 17.55 17.03" xmlns="http://www.w3.org/2000/svg">
<g stroke="none">
<path d="M6.21,4.36a3,3,0,0,1-1.8.6H1.21a.94.94,0,0,0-1,1v5.7a.94.94,0,0,0,1,1h4.2c.3.2.5.4.8.6l3.5,2.6a.47.47,0,0,0,.8-.4V2.06a.47.47,0,0,0-.8-.4Z"/>
</g>
<g fill="none" stroke-linecap="round" stroke-width="1.5px">
<path d="M13.11,1.18S17,.38,17,8.88s-3.9,7.8-3.9,7.8"/>
<path d="M11.81,5.08s2.6-.4,2.6,3.8-2.6,3.9-2.6,3.9"/>
</g>
</symbol>
<symbol id="icon-unmute" viewBox="0 0 18.2 17" xmlns="http://www.w3.org/2000/svg">
<g stroke="none">
<path d="M6.21,4.36a3,3,0,0,1-1.8.6H1.21a.94.94,0,0,0-1,1v5.7a.94.94,0,0,0,1,1h4.2c.3.2.5.4.8.6l3.5,2.6a.47.47,0,0,0,.8-.4V2.06a.47.47,0,0,0-.8-.4Z"/>
</g>
<g fill="none" stroke-linecap="round" stroke-width="2px">
<path d="M12,5.55l5.4,5.4M12,11l5.4-5.4"/>
</g>
</symbol>
<symbol id="icon-play" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg"><path d="M16.5 8.5c.3.1.4.5.2.8-.1.1-.1.2-.2.2l-11.4 7c-.5.3-.8.1-.8-.5V2c0-.5.4-.8.8-.5l11.4 7z"/></symbol>
<symbol id="icon-pause" viewBox="0 0 14 16" xmlns="http://www.w3.org/2000/svg"><path d="M1,0H3.2a.94.94,0,0,1,1,1V15a.94.94,0,0,1-1,1H1a.94.94,0,0,1-1-1V1A1,1,0,0,1,1,0Zm9.8,0H13a.94.94,0,0,1,1,1V15a.94.94,0,0,1-1,1H10.8a.94.94,0,0,1-1-1V1A1,1,0,0,1,10.8,0Z"/></symbol>
<symbol id="icon-replay" viewBox="0 0 17.52 15.97" xmlns="http://www.w3.org/2000/svg"><path d="M16.7,7.27a.81.81,0,0,1-.9.7c-.1,0-.2,0-.2-.1l-4.9-1.8c-.5-.2-.6-.6-.1-.8l6.2-3.6c.5-.3.8-.1.7.5l-.8,5.1Z"/><path d="M8.1,13.77a5.92,5.92,0,0,1-2.9-.7A5.77,5.77,0,0,1,2,7.87a6.21,6.21,0,0,1,6.3-6A6.15,6.15,0,0,1,13.9,6l.1-.1L16.1,7A8.1,8.1,0,0,0,7,.07,8.24,8.24,0,0,0,0,8a8.06,8.06,0,0,0,4.3,7,10.21,10.21,0,0,0,3.8,1,8.24,8.24,0,0,0,6.6-3.3l-1.8-.9a7,7,0,0,1-4.8,2Z"/></symbol>
<symbol id="icon-overlay-play" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
<g fill="#333" stroke="#fff" stroke-width="5px">
<path d="M2.5,40A37.5,37.5,0,1,1,40,77.5,37.51,37.51,0,0,1,2.5,40Z"/>
</g>
<g fill="#fff">
<path d="M60.3,38a1,1,0,0,1,.6,1.4.9.9,0,0,1-.6.6L30,57.5c-1,.6-1.7.1-1.7-1v-35c0-1.1.8-1.5,1.7-1Z"/>
</g>
</symbol>
<symbol id="icon-loading-spinner" viewBox="0 0 75.8 77.9" xmlns="http://www.w3.org/2000/svg">
<g fill="#fff">
<circle cx="38.8" cy="8.1" r="8.1"/>
<g opacity="0.4">
<circle cx="70.8" cy="40" r="5"/>
</g>
<g opacity="0.6">
<circle cx="38.8" cy="71.9" r="6"/>
</g>
<g opacity="0.8">
<circle cx="7" cy="40" r="7"/>
</g>
<g opacity="0.9">
<circle cx="15.1" cy="17.3" r="7.5"/>
</g>
<g opacity="0.3">
<circle cx="63.2" cy="17.1" r="4.5"/>
</g>
<g opacity="0.5">
<circle cx="62.7" cy="63.8" r="5.5"/>
</g>
<g opacity="0.7">
<circle cx="15.1" cy="63.8" r="6.5"/>
</g>
</g>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB