# Video Extraction Flow Analysis

## 1. ADMIN CONTROLLERS - VIDEO EXTRACTION TRIGGER

### Main Admin Video Controller
**File**: [app/Http/Controllers/Admin/VideoController.php](app/Http/Controllers/Admin/VideoController.php)

**Retry Extraction Endpoint** (Line 1099):
```php
public function retry(Video $video)
{
    // Route: POST /admin/videos/{video}/retry
    $this->adminService->retryEncode($video);
    return back()->with('success', 'Retry job dispatched');
}
```

**Edit Video - Reset Extract State** (Line 330-336):
- When `play_mode` is changed, extract state is reset
- Sets `extract_failed = false` and `last_extracted_at = null`
- Forces re-extraction on next view

**Export/Import Endpoints** (Line 1607-1608):
- Tracks `extract_failed` and `error_message` fields
- Filter videos by extraction status in list view

---

## 2. WHERE HLS EXTRACTION IS TRIGGERED

### A. **External Embed Controller** - Primary Entry Point
**File**: [app/Http/Controllers/EmbedController.php](app/Http/Controllers/EmbedController.php)

**Main Flow** (Line 106-140):
```php
private function playExternal(Video $video)
{
    // Line 123: Calls VideoService::resolvePlayMode()
    $result = $this->videoService->resolvePlayMode($video);
    
    // Line 124: Marks extraction failure in error message
    if ($result['mode'] === 'iframe' && $video->extract_failed) {
        $video->updateQuietly(['error_message' => 'Extract failed at ' . now()->format('Y-m-d H:i:s')]);
    }
}
```

### B. **VideoService** - Extraction Orchestrator
**File**: [app/Services/VideoService.php](app/Services/VideoService.php)

**Main Extraction Method** (Line 177-268):
```php
public function resolvePlayMode(Video $video): array
{
    // Line 187: Check if IP-locked (client-side extraction)
    if ($extractor->isIpLocked() && !$forceProxyHls) {
        return ['mode' => 'client_hls', ...];
    }
    
    // Line 215-235: Cache reuse (< 2 hours)
    if ($video->play_mode === 'hls' && $video->hls_url && $video->last_extracted_at->diffInHours(now()) < 2) {
        return ['mode' => $cachedMode, 'hls_url' => $video->hls_url, ...];
    }
    
    // Line 236-268: Fresh extraction attempt
    $hlsUrl = $this->attemptExtract($video);
    if ($hlsUrl) {
        $video->markExtractSuccess($hlsUrl);  // Line 241
        return ['mode' => 'hls', 'hls_url' => $hlsUrl, ...];
    }
    
    // Line 261-268: Extraction failed fallback
    $video->markExtractFailed();  // Line 261
    return ['mode' => 'iframe', ...];
}
```

**Attempt Extract** (Line 270-302):
```php
private function attemptExtract(Video $video): ?string
{
    $extractor = $this->getExtractor($video->provider);
    try {
        $hlsUrl = $extractor->extract($embedUrl);  // Line 278
        
        if ($hlsUrl && $this->looksLikeValidM3u8($hlsUrl)) {  // Line 280
            Log::channel('extractor')->info("Extract success", [
                'provider' => $video->provider,
                'hls_url' => $hlsUrl,
            ]);
            return $hlsUrl;
        }
        return null;
    } catch (\Throwable $e) {
        Log::channel('extractor')->error("Extract exception", [
            'provider' => $video->provider,
            'error' => $e->getMessage(),
        ]);
        return null;
    }
}
```

---

## 3. HLS VALIDATION - looksLikeValidM3u8()

**File**: [app/Services/VideoService.php](app/Services/VideoService.php) - Line 464-480

```php
private function looksLikeValidM3u8(string $url): bool
{
    // Line 466-467: Must be HTTP/HTTPS
    if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
        return false;
    }

    // Line 470: Contains .m3u8
    if (str_contains($url, '.m3u8')) {
        return true;
    }

    // Line 473-474: Contains .mp4 (direct video file)
    if (str_contains(strtolower($url), '.mp4')) {
        return true;
    }

    // Line 477: BunnyCDN bridge endpoint
    if (str_contains(strtolower($url), 'bridge.php')) {
        return true;
    }
    
    return false;
}
```

**Error Cases**:
- ❌ Returns `null` if URL doesn't match patterns
- ❌ Triggers fallback to iframe playback
- ✅ Sets `extract_failed = true` in database
- ✅ Logs as `"Extract exception"` with error details

---

## 4. CONSOLE COMMANDS FOR EXTRACTION

### A. Test Extraction Command
**File**: [app/Console/Commands/TestExtract.php](app/Console/Commands/TestExtract.php)

**Usage**: `php artisan video:test-extract {url}`

```php
public function handle(VideoService $videoService): int
{
    // Line 27: Get extractor for provider
    $extractor = $videoService->getExtractorFor($provider);
    $hlsUrl = $extractor->extract($url);  // Line 28
    
    if ($hlsUrl) {
        $this->info("✅ HLS extraction SUCCESS");
        $this->line("HLS URL: {$hlsUrl}");
    } else {
        $this->warn("⚠️  HLS extraction FAILED → iframe fallback will be used");
    }
}
```

### B. Migration Command
**File**: [app/Console/Commands/MigrateExternalVideos.php](app/Console/Commands/MigrateExternalVideos.php)

**Handles extraction failures** (Line 82):
```php
->where('extract_failed', true)  // Targets failed extractions
```

### C. Storage Analysis Command
**File**: [app/Console/Commands/AnalyzeVideoStorage.php](app/Console/Commands/AnalyzeVideoStorage.php)

**Reports extraction failures** (Line 72, 77, 83):
```php
SUM(CASE WHEN extract_failed = 1 THEN 1 ELSE 0 END) as extract_failed
// Shows count of videos with extraction failures
```

---

## 5. ADMIN DASHBOARD EXTRACTION TRIGGERS

### Route Definitions
**File**: [routes/web.php](routes/web.php) - Line 77-137

**Admin Video Routes**:
```php
// Line 77: Retry failed extraction/encoding
Route::post('videos/{video}/retry', [AdminVideoController::class, 'retry'])->name('videos.retry');

// Line 78-79: Enable/Disable
Route::post('videos/{video}/disable', [AdminVideoController::class, 'disable'])->name('videos.disable');
Route::post('videos/{video}/enable', [AdminVideoController::class, 'enable'])->name('videos.enable');

// Line 81: Share video (for testing)
Route::get('videos/{video}/share', [AdminVideoController::class, 'share'])->name('videos.share');
```

### Admin Service - Retry Implementation
**File**: [app/Services/VideoAdminService.php](app/Services/VideoAdminService.php) - Line 303-325

```php
public function retryEncode(Video $video): void
{
    if (!$video->canRetryEncode()) {
        throw new \RuntimeException(
            "Video [{$video->id}] cannot retry encoding (status: {$video->status})"
        );
    }
    
    Log::channel('video_admin')->info('Retrying encoding', [
        'video_id' => $video->id,
        'previous_error' => $video->error_message,
    ]);
    
    // Line 324: Dispatches EncodeVideoToHlsJob
    $this->dispatchEncode($video);
}
```

**Dispatch Encode** (Line 235-280):
- Requires status: `uploaded` or `failed`
- Sets status to `encoding`
- Dispatches `EncodeVideoToHlsJob` with all video parameters
- Clears error_message

---

## 6. ERROR HANDLING & LOGGING

### Extraction Errors

**Log Channel**: `extractor`

**Log Locations**:

| Location | Type | When |
|----------|------|------|
| [VideoService::attemptExtract()](app/Services/VideoService.php#L280) | INFO | Extraction succeeds |
| [VideoService::attemptExtract()](app/Services/VideoService.php#L295) | ERROR | Extract throws exception |
| [VideoService::resolvePlayMode()](app/Services/VideoService.php#L260) | WARNING | Extract failed, iframe fallback |
| [EmbedController::playExternal()](app/Http/Controllers/EmbedController.php#L124) | UPDATE | Sets error_message to "Extract failed at {timestamp}" |

### Error Tracking in Database

**Video Model Fields** (Line 38-40, 52-53):
- `extract_failed` (boolean) - failure flag
- `hls_url` (string, nullable) - cached HLS URL
- `last_extracted_at` (datetime) - last extraction attempt
- `error_message` (text) - human-readable error

**Model Methods** ([app/Models/Video.php](app/Models/Video.php) - Line 157-176):

```php
public function markExtractSuccess(string $hlsUrl): void
{
    $this->update([
        'play_mode' => 'hls',
        'hls_url' => $hlsUrl,
        'extract_failed' => false,
        'last_extracted_at' => now(),
    ]);
}

public function markExtractFailed(): void
{
    $this->update([
        'play_mode' => $this->play_mode === 'auto' ? 'iframe' : $this->play_mode,
        'hls_url' => null,
        'extract_failed' => true,
        'last_extracted_at' => now(),
    ]);
}
```

---

## 7. EXTRACTOR INTERFACE & PROVIDER MAPPING

**File**: [app/Services/Extractors/Contracts/HlsExtractorInterface.php](app/Services/Extractors/Contracts/HlsExtractorInterface.php)

```php
interface HlsExtractorInterface
{
    // Main extraction method
    public function extract(string $embedUrl): ?string;
    
    // Provider name
    public function provider(): string;
    
    // IP-locked detection (triggers client-side extraction)
    public function isIpLocked(): bool;
    
    // Client-side extraction config
    public function getClientExtractConfig(string $embedUrl): ?array;
}
```

**Provider Registry** (VideoService::$extractors - [Line 106-107](app/Services/VideoService.php#L106)):
```php
'streamhls'   => StreamhlsExtractor::class,
'savefiles'   => StreamhlsExtractor::class,
// ... 50+ providers mapped to extractors
```

---

## 8. FLOW DIAGRAM

```
┌─────────────────────────────────────────────────────────────┐
│           User Views Video (/e/{video})                     │
└────────────────────┬────────────────────────────────────────┘
                     │
                     ▼
        ┌─────────────────────────────┐
        │  EmbedController::show()    │
        │  (plays external embed)     │
        └────────────┬────────────────┘
                     │
                     ▼
        ┌──────────────────────────────────┐
        │  VideoService::resolvePlayMode() │
        └────────┬────────────────┬────────┘
                 │                │
        ┌────────▼────────┐  ┌────▼─────────────────┐
        │ IP-Locked Check │  │ Cached HLS Valid?    │
        │ (Line 187)      │  │ (< 2 hours, Line 215)│
        │ ├─ YES          │  │ ├─ YES → Use Cache   │
        │ │ ├─ Config OK? │  │ └─ NO                │
        │ │ │ ├─ YES      │  │    (Continue...)     │
        │ │ │ │ → client  │  │                      │
        │ │ │ └─ NO       │  │                      │
        │ │ │   → iframe  │  │                      │
        │ └─ NO → fresh   │  │                      │
        │      extract    │  │                      │
        └─────────────────┘  │                      │
                             │                      │
                ┌────────────▼──────────────┐
                │  attemptExtract()        │
                │  (Line 270)              │
                └────────┬────────────────┘
                         │
                         ▼
              ┌──────────────────────┐
              │ Get Provider Extract │
              │ (Line 276)           │
              └────────┬─────────────┘
                       │
                       ▼
        ┌──────────────────────────────┐
        │ extractor->extract($url)     │
        │ (Line 278)                   │
        └────┬──────────────────┬──────┘
             │                  │
       ┌─────▼────────┐   ┌─────▼──────────────┐
       │  Returns URL │   │ Returns NULL/fails │
       │              │   │                    │
       │ ┌────────────▼─┐ │  ┌────────────────▼──┐
       │ │ Validate with│ │  │ Log Extract Error │
       │ │ looksLike    │ │  │ (Line 295)        │
       │ │ ValidM3u8()  │ │  │                   │
       │ │ (Line 280)   │ │  │ Return null       │
       │ └─┬─────────┬──┘ │  └─────────┬────────┘
       │   │         │    │            │
       │   │VALID    │    │            │
       │   │         │    │            │
       ├───▼──────┐  │    │         ┌──▼──────────┐
       │ markExt  │  │INVALID      │ markExtract │
       │ Success  │  │             │ Failed()    │
       │ (L241)   │  │             │ (Line 261)  │
       │          │  │             │             │
       │ extract_ │  │             │ extract_    │
       │ failed=  │  │             │ failed=true │
       │ FALSE    │  └─────────────│ (Line 174)  │
       │          │                └──────┬──────┘
       │ Return   │                       │
       │ HLS URL  │              Return NULL
       │ (L292)   │              (L263)
       └────┬─────┘                │
            │                      │
            ▼                      ▼
       ┌─────────────────────┬──────────────┐
       │ MODE: 'hls' / etc   │ MODE: 'iframe' │
       │ ┌────────────────┐  │ ┌──────────────┤
       │ │ Render HLS     │  │ │ Render embed │
       │ │ Player         │  │ │ <iframe>     │
       │ └────────────────┘  │ │ (no extract) │
       │                     │ └──────────────┤
       └─────────────────────┴──────────────┘
```

---

## 9. KEY CODE PATHS SUMMARY

| Operation | Controller | Service | Model | Status Field |
|-----------|-----------|---------|-------|--------------|
| **Extract on View** | [EmbedController::playExternal()](app/Http/Controllers/EmbedController.php#L106) | [VideoService::resolvePlayMode()](app/Services/VideoService.php#L177) | Video | `extract_failed` |
| **Validate URL** | - | [VideoService::looksLikeValidM3u8()](app/Services/VideoService.php#L464) | - | - |
| **Attempt Extract** | - | [VideoService::attemptExtract()](app/Services/VideoService.php#L270) | - | - |
| **Mark Success** | - | - | [Video::markExtractSuccess()](app/Models/Video.php#L159) | extract_failed=FALSE |
| **Mark Failure** | - | - | [Video::markExtractFailed()](app/Models/Video.php#L169) | extract_failed=TRUE |
| **Admin Retry** | [VideoController::retry()](app/Http/Controllers/Admin/VideoController.php#L1099) | [VideoAdminService::retryEncode()](app/Services/VideoAdminService.php#L311) | Video | status→encoding |
| **Test Extract** | - | [VideoService::getExtractorFor()](app/Services/VideoService.php#L551) | - | - |

---

## 10. CACHING STRATEGY

**Cache TTL**: 2 hours (Line 220 in VideoService)

```php
if (
    !$mustExtractFresh
    && $video->play_mode === 'hls'
    && $video->hls_url
    && $video->last_extracted_at
    && $video->last_extracted_at->diffInHours(now()) < 2
) {
    // Reuse cached HLS URL
}
```

**Always Fresh Providers** (Token-based):
- Defined in `$alwaysFreshExtractProviders` array
- Extracted every time view is requested
- Used for providers with time-limited tokens

---

## Files to Review

1. **Main Orchestrator**: [app/Services/VideoService.php](app/Services/VideoService.php)
   - Lines 177-302: Full extraction flow
   - Lines 464-480: HLS validation

2. **Entry Point**: [app/Http/Controllers/EmbedController.php](app/Http/Controllers/EmbedController.php)
   - Lines 106-140: External embed player with extraction

3. **Database Tracking**: [app/Models/Video.php](app/Models/Video.php)
   - Lines 157-176: Extract success/failure marking

4. **Admin Trigger**: [app/Services/VideoAdminService.php](app/Services/VideoAdminService.php)
   - Lines 311-324: Retry extraction/encoding

5. **Extractor Interface**: [app/Services/Extractors/Contracts/HlsExtractorInterface.php](app/Services/Extractors/Contracts/HlsExtractorInterface.php)

6. **Logging**: All extraction logs use `Log::channel('extractor')`
