I solved it myself by the following code.
1. Create a custom RepeaterPage class for my tracks and add a specific method to create a session-based URL for streaming.
<?php namespace ProcessWire;
/**
* @property Pagefile $audio
*/
class TracksRepeaterPage extends RepeaterPage
{
/**
* @param string $streamUrl Default stream URL when audio file does not exist
*
* @return string
*/
public function getStreamUrl(string $streamUrl = ''): string
{
// Check whether audio file exists and if it does create a session-based stream URL
if ($this->audio) {
// Create the file hash and add it to the session as key for the audio file path
$audioPath = $this->audio->filename();
$fileHash = hash('sha1', $audioPath . session_id());
session()->setFor('stream', $fileHash, $audioPath);
// Create the stream URL with the file hash
$streamUrl = '/stream/' . $fileHash . '.mp3';
}
return $streamUrl;
}
}
2. Create a custom hook to watch for the stream URL in the ready.php:
<?php namespace ProcessWire;
if (!defined("PROCESSWIRE")) {
die();
}
wire()->addHook('/stream/([\w\d]+).mp3', function ($event) {
// Get the audio file path by file hash
$fileHash = $event->arguments(1);
$audioPath = session()->getFor('stream', $fileHash);
// Check whether audio file exists and stream it when it does or throw 404
if (file_exists($audioPath)) {
wireSendFile($audioPath, [], [
'content-transfer-encoding' => 'chunked',
'accept-ranges' => 'bytes',
'cache-control' => 'no-cache'
]);
}
throw new Wire404Exception();
});
Ta da! 🙂 When the session expires, the session-based file hashes are destroyed and the stream URL no longer work. So every time the session is renewed with a new session ID, a new unique session-based stream URL is generated for each tracks.
Have I missed anything or are there any security issues?