feat(shorts): single reused <video> element + 1-2 next preloaded
The Shorts feed used to emit one <video id="sv-{idx}"> per short. With
60 shorts, that was 60 HTMLMediaElement allocations even though only
one plays at a time — bandwidth was bounded by preload="none" but the
DOM shape was still N:1.
Replace with the YT-parity architecture:
- One #svGlobal <video> rendered once at the end of the feed. The
IntersectionObserver docks it into the active .short-player-wrap on
entry and sets its src from the slot's data-src. Browsers preserve
the active media context across node moves so playback doesn't
restart just because we re-parented the node.
- Per-slot .short-poster shim keeps the slot filled with the video's
thumbnail while the shared player is docked elsewhere; the slot
never goes blank between swaps.
- Two #svPre1 / #svPre2 preloaders park at the end of the DOM and get
their src set to the NEXT one and two shorts on each dock. preload=
"metadata" grabs the moov atom + first keyframe only — enough that
the switch to playback is instant, cheap enough that we can run two.
- The <video>'s single timeupdate listener now updates the
.short-progress-fill of whichever slot it's currently docked in,
found via video.parentElement.id.replace('wrap-', '').
- toggleMute now mutes the shared video directly instead of looking
up the no-longer-existent sv-{idx} by id.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
c59b36ebf7
commit
e76ac39f99
2 files changed
+193
-38
No files matched your search
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Http\Middleware\VerifyCsrfToken;
|
||||
use App\Models\User;
|
||||
use App\Models\Video;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* Shorts: single reused <video> element + 1-2 next preloaded.
|
||||
*
|
||||
* Validates that the shorts feed markup matches the architecture:
|
||||
* - a single #svGlobal <video> rendered once at the end of the forelse,
|
||||
* - two preloader placeholders (#svPre1 / #svPre2),
|
||||
* - no per-short <video> elements inside .short-player-wrap,
|
||||
* - each slot carries its own data-src + data-poster so the shared player
|
||||
* can swap into position without a full DOM rebuild.
|
||||
*/
|
||||
class ShortsReusedVideoTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
putenv('DB_CONNECTION=sqlite');
|
||||
putenv('DB_DATABASE=:memory:');
|
||||
$_ENV['DB_CONNECTION'] = 'sqlite';
|
||||
$_ENV['DB_DATABASE'] = ':memory:';
|
||||
|
||||
parent::setUp();
|
||||
|
||||
$this->withoutMiddleware([
|
||||
VerifyCsrfToken::class,
|
||||
\Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_shorts_feed_renders_a_single_shared_video_element(): void
|
||||
{
|
||||
$u = User::factory()->create([
|
||||
'username' => 'sh-'.substr(bin2hex(random_bytes(3)), 0, 6),
|
||||
]);
|
||||
// Three shorts so we can prove only ONE <video id="svGlobal"> is
|
||||
// emitted no matter how many shorts there are.
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
Video::create([
|
||||
'user_id' => $u->id,
|
||||
'title' => 'Short '.$i,
|
||||
'description' => '',
|
||||
'filename' => "users/x/videos/y{$i}/video.mp4",
|
||||
'path' => "users/x/videos/y{$i}/video.mp4",
|
||||
'type' => 'generic',
|
||||
'visibility' => 'public',
|
||||
'status' => 'ready',
|
||||
'is_shorts' => true,
|
||||
'duration' => 45,
|
||||
'orientation' => 'portrait',
|
||||
'share_token' => bin2hex(random_bytes(8)),
|
||||
]);
|
||||
}
|
||||
|
||||
$html = $this->get(route('videos.shorts'))->assertOk()->getContent();
|
||||
|
||||
// Exactly one shared video element and two preloaders.
|
||||
$this->assertEquals(
|
||||
1,
|
||||
substr_count($html, 'id="svGlobal"'),
|
||||
'Shorts feed must emit exactly one shared <video id="svGlobal">.'
|
||||
);
|
||||
$this->assertStringContainsString('id="svPre1"', $html);
|
||||
$this->assertStringContainsString('id="svPre2"', $html);
|
||||
|
||||
// No per-short video elements inside the slot wraps.
|
||||
$this->assertDoesNotMatchRegularExpression(
|
||||
'/short-player-wrap[^>]*>[^<]*<video id="sv-\d+/',
|
||||
$html,
|
||||
'Per-short <video id="sv-{idx}"> elements should be gone.'
|
||||
);
|
||||
|
||||
// Each slot still carries its own src + poster for the shared
|
||||
// player to pick up on dock.
|
||||
$this->assertStringContainsString('data-src=', $html);
|
||||
$this->assertStringContainsString('data-poster=', $html);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user