Chunked video upload, persistent track order, per-track playlist entries

Chunked uploads: bypass Cloudflare's 100 MB request cap by slicing the
video/audio into 8 MB chunks and reassembling server-side before the
normal store flow runs.

Track reorder: audio tracks gain a `position` column so a user's edit-
modal ordering survives reload. `Video::audioTracks()` now orders by
position, `promote_track_id` remembers the swapped row, and freshly
uploaded tracks append to the end.

Playlist per-track pinning: `playlist_videos.audio_track_id` remembers
which language the user pinned when adding a song. Same song with a
different track becomes a separate row (Spotify-style duplicates).
Player-data exposes `active_stream_url` + `active_track_id`, sidebar
cards render `?track=` from the pivot, and both `plTransitionTo` and
initial-load code activate the corresponding language option.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
ghassanandClaude Opus 4.7 committed 2026-07-31 02:42:25 +03:00
1 parent af276f2cd1
commit ba8ab11ce4
12 files changed
+547 -49

No files matched your search

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('video_audio_tracks', function (Blueprint $table) {
$table->unsignedInteger('position')->default(0)->after('label');
$table->index(['video_id', 'position']);
});
// Backfill existing rows so their current id-order becomes the
// canonical position. New rows start at 0 and get placed at the
// end by the update handler.
DB::statement('UPDATE video_audio_tracks SET position = id');
}
public function down(): void
{
Schema::table('video_audio_tracks', function (Blueprint $table) {
$table->dropIndex(['video_id', 'position']);
$table->dropColumn('position');
});
}
};
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('playlist_videos', function (Blueprint $table) {
// Nullable — null means "play the primary track".
$table->unsignedBigInteger('audio_track_id')
->nullable()
->after('video_id');
// Drop the old (playlist_id, video_id) uniqueness so the same
// video can appear multiple times with different tracks.
$table->dropUnique(['playlist_id', 'video_id']);
$table->index(['playlist_id', 'video_id', 'audio_track_id'], 'playlist_video_track_idx');
});
}
public function down(): void
{
Schema::table('playlist_videos', function (Blueprint $table) {
$table->dropIndex('playlist_video_track_idx');
$table->unique(['playlist_id', 'video_id']);
$table->dropColumn('audio_track_id');
});
}
};