Detect Vimeo URLs and emit iframe with dnt=1

Recognizes vimeo.com/{id} and player.vimeo.com/video/{id}. Preserves
timestamps from #t=30s and ?t=30s as #t=30s on the embed URL (Vimeo
convention).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yutaka Kurosaki
2026-05-09 10:57:05 +09:00
parent 9486d97c73
commit 6ee4dcfc21
2 changed files with 68 additions and 1 deletions
+27 -1
View File
@@ -15,7 +15,8 @@ public function resolve(string $url): ?string
} }
return $this->detectVideo($url) return $this->detectVideo($url)
?? $this->detectAudio($url) ?? $this->detectAudio($url)
?? $this->detectYouTube($url); ?? $this->detectYouTube($url)
?? $this->detectVimeo($url);
} }
private function detectVideo(string $url): ?string private function detectVideo(string $url): ?string
@@ -98,6 +99,31 @@ private function parseTimestamp(string $t): ?int
return $matched ? $total : null; return $matched ? $total : null;
} }
private function detectVimeo(string $url): ?string
{
if (!preg_match('~^https?://(?:www\.|player\.)?vimeo\.com/(?:video/)?(\d+)~', $url, $m)) {
return null;
}
$videoId = $m[1];
$src = "https://player.vimeo.com/video/{$videoId}?dnt=1";
$hash = $this->extractVimeoHash($url);
if ($hash !== null) {
$src .= '#' . $hash;
}
return $this->iframeHtml($src, 'vimeo');
}
private function extractVimeoHash(string $url): ?string
{
if (preg_match('/#(t=[^&]+)/', $url, $m)) {
return $m[1];
}
if (preg_match('/[?&](t=[^&#]+)/', $url, $m)) {
return $m[1];
}
return null;
}
private function iframeHtml(string $src, string $provider): string private function iframeHtml(string $src, string $provider): string
{ {
$safe = htmlspecialchars($src, ENT_QUOTES, 'UTF-8'); $safe = htmlspecialchars($src, ENT_QUOTES, 'UTF-8');
@@ -145,4 +145,45 @@ public static function youtubeTimestampUrls(): array
'start=45' => ['https://www.youtube.com/watch?v=dQw4w9WgXcQ&start=45', 45], 'start=45' => ['https://www.youtube.com/watch?v=dQw4w9WgXcQ&start=45', 45],
]; ];
} }
#[DataProvider('vimeoUrls')]
public function test_vimeo_urls_produce_iframe(string $url): void
{
$html = $this->resolver->resolve($url);
$this->assertNotNull($html);
$this->assertStringStartsWith('<iframe', $html);
$this->assertStringContainsString('player.vimeo.com/video/123456789', $html);
$this->assertStringContainsString('dnt=1', $html);
$this->assertStringContainsString('class="kb-embed kb-embed-vimeo"', $html);
}
public static function vimeoUrls(): array
{
return [
'vimeo.com' => ['https://vimeo.com/123456789'],
'www.vimeo.com' => ['https://www.vimeo.com/123456789'],
'player.vimeo.com' => ['https://player.vimeo.com/video/123456789'],
];
}
#[DataProvider('vimeoTimestampUrls')]
public function test_vimeo_timestamp_preserved_as_hash(string $url): void
{
$html = $this->resolver->resolve($url);
$this->assertNotNull($html);
$this->assertStringContainsString('#t=30s', $html);
}
public static function vimeoTimestampUrls(): array
{
return [
'hash form' => ['https://vimeo.com/123456789#t=30s'],
'query form' => ['https://vimeo.com/123456789?t=30s'],
];
}
public function test_vimeo_invalid_id_returns_null(): void
{
$this->assertNull($this->resolver->resolve('https://vimeo.com/notanumber'));
}
} }