fix: count active HLS playback renditions
This commit is contained in:
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user