fix: count active HLS playback renditions

This commit is contained in:
root
2026-09-05 04:34:32 +02:00
parent 41a0791901
commit b299fb9f29
4 changed files with 119 additions and 7 deletions
+6 -1
View File
@@ -34,6 +34,11 @@ final readonly class AssetAuthorizer
$directory = $this->paths->asset($stationTempDirectory, $session->cacheKey);
$path = ResourcePath::resolve($directory, $resource);
return null === $path ? null : ['session' => $session, 'path' => $path];
if (null === $path) {
return null;
}
$this->sessions->markAccessed($stationTempDirectory, $token, $now);
return ['session' => $session, 'path' => $path];
}
}
+38 -4
View File
@@ -13,6 +13,12 @@ use Throwable;
final readonly class SessionRepository
{
/**
* HLS clients normally request a playlist or segment every few seconds.
* This intentionally measures active playback rather than issued URLs.
*/
private const int ACTIVE_PLAYBACK_WINDOW = 90;
public function __construct(
private Config $config,
private CachePaths $paths,
@@ -72,6 +78,7 @@ final readonly class SessionRepository
@unlink($temporary);
throw new RuntimeException('Could not publish playback session.');
}
@touch($target, $now);
return ['token' => $token, 'session' => $session];
} finally {
@@ -114,7 +121,8 @@ final readonly class SessionRepository
return 0;
}
$count = 0;
/** @var array<string, true> $activeCacheKeys */
$activeCacheKeys = [];
foreach (new \FilesystemIterator($directory, \FilesystemIterator::SKIP_DOTS) as $file) {
if (!$file->isFile() || $file->isLink() || 1 !== preg_match('/^[a-f0-9]{64}\.json$/D', $file->getFilename())) {
continue;
@@ -124,15 +132,35 @@ final readonly class SessionRepository
$data = false !== $raw ? json_decode($raw, true, 32, JSON_THROW_ON_ERROR) : null;
if (is_array($data)) {
$session = PlaybackSession::fromArray($data);
if (!$session->isExpired($now) && hash_equals($session->principalHash, $principalHash)) {
++$count;
if (!$session->isExpired($now)
&& hash_equals($session->principalHash, $principalHash)
&& $this->isRecentlyAccessed($file, $now)) {
// Multiple URLs for the same rendition are usually client retries or
// duplicate player initialization. They consume one playback slot.
$activeCacheKeys[$session->cacheKey] = true;
}
}
} catch (Throwable) {
continue;
}
}
return $count;
return count($activeCacheKeys);
}
public function markAccessed(string $stationTempDirectory, string $token, ?int $now = null): void
{
if (!OpaqueToken::isValid($token)) {
return;
}
$path = $this->sessionPath($stationTempDirectory, OpaqueToken::id($token));
if (is_link($path) || !is_file($path)) {
return;
}
// The session file contains no bearer token. Its mtime is a lightweight,
// race-tolerant playback heartbeat and is refreshed only after authorization.
@touch($path, $now ?? time());
}
private function sessionPath(string $stationTempDirectory, string $id): string
@@ -142,4 +170,10 @@ final readonly class SessionRepository
}
return $this->paths->sessions($stationTempDirectory) . '/' . $id . '.json';
}
private function isRecentlyAccessed(\SplFileInfo $file, int $now): bool
{
$lastAccessedAt = $file->getMTime();
return $lastAccessedAt >= $now - self::ACTIVE_PLAYBACK_WINDOW;
}
}