['timeout' => 5, 'ignore_errors' => true]]); $raw = @file_get_contents($url, false, $ctx); return $raw ? (json_decode($raw, true) ?? []) : []; } switch ($action) { case 'sessions': $sessions = jf_get('/Sessions'); $active = array_filter($sessions, fn($s) => isset($s['NowPlayingItem'])); $out = []; foreach ($active as $s) { $np = $s['NowPlayingItem']; $pos = $s['PlayState']['PositionTicks'] ?? 0; $dur = $np['RunTimeTicks'] ?? 0; $out[] = [ 'session_id' => $s['Id'], 'user' => $s['UserName'] ?? 'Unknown', 'device' => $s['DeviceName'] ?? '', 'client' => $s['Client'] ?? '', 'title' => $np['Name'] ?? '', 'type' => $np['Type'] ?? '', 'series' => $np['SeriesName'] ?? null, 'year' => $np['ProductionYear'] ?? null, 'paused' => $s['PlayState']['IsPaused'] ?? false, 'position_pct'=> $dur > 0 ? round($pos / $dur * 100) : 0, ]; } echo json_encode(['sessions' => array_values($out), 'total_active' => count($out)]); break; case 'library': $movies = jf_get('/Items?IncludeItemTypes=Movie&Recursive=true&Limit=0'); $series = jf_get('/Items?IncludeItemTypes=Series&Recursive=true&Limit=0'); $episodes= jf_get('/Items?IncludeItemTypes=Episode&Recursive=true&Limit=0'); echo json_encode([ 'movies' => $movies['TotalRecordCount'] ?? 0, 'series' => $series['TotalRecordCount'] ?? 0, 'episodes' => $episodes['TotalRecordCount'] ?? 0, ]); break; case 'search': $q = trim($_GET['q'] ?? ''); if (!$q) { echo json_encode(['results' => []]); break; } $data = jf_get('/Search/Hints?SearchTerm=' . urlencode($q) . '&Limit=8&IncludeItemTypes=Movie,Series,Episode'); $hints = $data['SearchHints'] ?? []; $results = array_map(fn($h) => [ 'id' => $h['ItemId'], 'name' => $h['Name'], 'type' => $h['Type'], 'year' => $h['ProductionYear'] ?? null, 'series'=> $h['Series'] ?? null, ], $hints); echo json_encode(['results' => $results]); break; case 'recent': $data = jf_get('/Items/Latest?Limit=8&IncludeItemTypes=Movie,Episode&Fields=Overview'); $out = array_map(fn($i) => [ 'name' => $i['Name'], 'type' => $i['Type'], 'series' => $i['SeriesName'] ?? null, 'year' => $i['ProductionYear'] ?? null, ], is_array($data) ? $data : []); echo json_encode(['recent' => $out]); break; case 'control': $jfSessionId = $_GET['session_id'] ?? ''; $jfCommand = $_GET['command'] ?? ''; // General commands supported by Jellyfin session control $allowed = ['TogglePause', 'Stop', 'NextTrack', 'PreviousTrack', 'VolumeUp', 'VolumeDown']; if (!$jfSessionId || !in_array($jfCommand, $allowed, true)) { // No session_id: get the first active session automatically if (!$jfSessionId && in_array($jfCommand, $allowed, true)) { $sessions = jf_get('/Sessions'); foreach ($sessions as $s) { if (isset($s['NowPlayingItem'])) { $jfSessionId = $s['Id']; break; } } } if (!$jfSessionId) { echo json_encode(['error' => 'No active session or invalid command']); break; } } $url = JELLYFIN_URL . '/Sessions/' . rawurlencode($jfSessionId) . '/Command/' . $jfCommand . '?api_key=' . JELLYFIN_API_KEY; $ctx = stream_context_create(['http' => [ 'method' => 'POST', 'timeout' => 5, 'header' => 'Content-Type: application/json', 'content' => '{}', 'ignore_errors' => true, ]]); @file_get_contents($url, false, $ctx); echo json_encode(['success' => true, 'command' => $jfCommand, 'session_id' => $jfSessionId]); break; default: echo json_encode(['error' => 'Unknown action']); }