'EmbyLiveTVTracker', 'enabled' => strpos('personal', $this->config['license']) !== false, 'image' => 'plugins/images/homepage/embyLiveTVTracker.png', 'category' => 'Media Server', 'settingsArray' => __FUNCTION__ ]; if ($infoOnly) { return $homepageInformation; } $homepageSettings = [ 'debug' => true, 'settings' => [ 'Enable' => [ $this->settingsOption('enable', 'homepageEmbyLiveTVTrackerEnabled'), $this->settingsOption('auth', 'homepageEmbyLiveTVTrackerAuth'), ], 'Connection' => [ $this->settingsOption('url', 'embyURL'), $this->settingsOption('token', 'embyToken'), $this->settingsOption('disable-cert-check', 'embyDisableCertCheck'), $this->settingsOption('use-custom-certificate', 'embyUseCustomCertificate'), ], 'Display Options' => [ $this->settingsOption('number', 'homepageEmbyLiveTVTrackerRefresh', ['label' => 'Auto-refresh Interval (minutes)', 'min' => 1, 'max' => 60]), $this->settingsOption('switch', 'homepageEmbyLiveTVTrackerCompactView', ['label' => 'Use Compact View']), $this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowDuration', ['label' => 'Show Recording Duration']), $this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowSeriesInfo', ['label' => 'Show Series Information']), $this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowUserInfo', ['label' => 'Show User Information']), $this->settingsOption('number', 'homepageEmbyLiveTVTrackerMaxItems', ['label' => 'Maximum Scheduled Items', 'min' => 5, 'max' => 50]), $this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowCompleted', ['label' => 'Show Completed Recordings']), $this->settingsOption('number', 'homepageEmbyLiveTVTrackerDaysShown', ['label' => 'Days of Completed Recordings', 'min' => 1, 'max' => 30]), $this->settingsOption('number', 'homepageEmbyLiveTVTrackerMaxCompletedItems', ['label' => 'Maximum Completed Items', 'min' => 5, 'max' => 50]), $this->settingsOption('switch', 'homepageEmbyLiveTVTrackerDebug', ['label' => 'Enable Debug Logging']), ], 'Test Connection' => [ $this->settingsOption('blank', null, ['label' => 'Please Save before Testing']), $this->settingsOption('test', 'embyLiveTVTracker'), ] ] ]; return array_merge($homepageInformation, $homepageSettings); } public function testConnectionEmbyLiveTVTracker() { if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('test'), true)) { return false; } $url = $this->qualifyURL($this->config['embyURL']); $url = $url . "/emby/System/Info?api_key=" . $this->config['embyToken']; $options = $this->requestOptions($url, null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']); try { $response = Requests::get($url, [], $options); if ($response->success) { $info = json_decode($response->body, true); if (isset($info['ServerName'])) { // Test LiveTV functionality $liveTvUrl = $this->qualifyURL($this->config['embyURL']) . '/emby/LiveTv/Info?api_key=' . $this->config['embyToken']; try { $liveTvResponse = Requests::get($liveTvUrl, [], $options); $liveTvInfo = json_decode($liveTvResponse->body, true); $hasLiveTV = isset($liveTvInfo['Services']) && count($liveTvInfo['Services']) > 0; $message = 'Successfully connected to ' . $info['ServerName']; if ($hasLiveTV) { $message .= ' with LiveTV support enabled'; } else { $message .= ' (Warning: LiveTV may not be configured)'; } $this->setAPIResponse('success', $message, 200); } catch (Exception $e) { $this->setAPIResponse('success', 'Connected to ' . $info['ServerName'] . ' but LiveTV status unknown', 200); } } else { $this->setAPIResponse('error', 'Invalid response from Emby server', 500); } return true; } else { $this->setAPIResponse('error', 'Emby Connection Error', 500); return false; } } catch (Requests_Exception $e) { $this->setResponse(500, $e->getMessage()); return false; } } public function embyLiveTVTrackerHomepagePermissions($key = null) { $permissions = [ 'test' => [ 'enabled' => [ 'homepageEmbyLiveTVTrackerEnabled', ], 'auth' => [ 'homepageEmbyLiveTVTrackerAuth', ], 'not_empty' => [ 'embyURL', 'embyToken' ] ], 'main' => [ 'enabled' => [ 'homepageEmbyLiveTVTrackerEnabled' ], 'auth' => [ 'homepageEmbyLiveTVTrackerAuth' ], 'not_empty' => [ 'embyURL', 'embyToken' ] ] ]; return $this->homepageCheckKeyPermissions($key, $permissions); } public function homepageOrderEmbyLiveTVTracker() { if ($this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'))) { $refreshInterval = ($this->config['homepageEmbyLiveTVTrackerRefresh'] ?? 5) * 60000; // Convert minutes to milliseconds $compactView = ($this->config['homepageEmbyLiveTVTrackerCompactView'] ?? false) ? 'true' : 'false'; $showDuration = ($this->config['homepageEmbyLiveTVTrackerShowDuration'] ?? true) ? 'true' : 'false'; $showSeriesInfo = ($this->config['homepageEmbyLiveTVTrackerShowSeriesInfo'] ?? true) ? 'true' : 'false'; $showUserInfo = ($this->config['homepageEmbyLiveTVTrackerShowUserInfo'] ?? false) ? 'true' : 'false'; $maxItems = $this->config['homepageEmbyLiveTVTrackerMaxItems'] ?? 10; $showCompleted = ($this->config['homepageEmbyLiveTVTrackerShowCompleted'] ?? true) ? 'true' : 'false'; $daysShown = $this->config['homepageEmbyLiveTVTrackerDaysShown'] ?? 7; $maxCompletedItems = $this->config['homepageEmbyLiveTVTrackerMaxCompletedItems'] ?? 5; $panelClass = ($compactView === 'true') ? 'panel-compact' : ''; $statsClass = ($compactView === 'true') ? 'col-sm-6' : 'col-sm-3'; return '
Emby LiveTV Tracker

-

Active Timers

-

Series Timers
' . (($compactView === 'false') ? '

-

Today\'s Recordings

-

Total Recordings
' : '') . '

Scheduled Recordings Upcoming and active timers

' . (($showSeriesInfo === 'true') ? '' : '') . ' ' . (($showUserInfo === 'true') ? '' : '') . ' ' . (($showDuration === 'true') ? '' : '') . '
Date SeriesEpisode TitleChannelUserDurationStatus
Loading...
' . (($showCompleted === 'true') ? '

Completed Recordings Recent recordings

' . (($showSeriesInfo === 'true') ? '' : '') . ' ' . (($showDuration === 'true') ? '' : '') . '
Date SeriesSeriesDurationStatus
Loading...
' : '') . '
'; } } public function getHomepageEmbyLiveTVStats() { if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'), true)) { return false; } if (!$this->config['embyURL'] || !$this->config['embyToken']) { $this->setAPIResponse('error', 'Emby URL or Token not configured', 500); return false; } try { $options = $this->requestOptions($this->config['embyURL'], null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']); $baseUrl = $this->qualifyURL($this->config['embyURL']); $stats = [ 'activeTimers' => 0, 'seriesTimers' => 0, 'todaysRecordings' => 0, 'totalRecordings' => 0, 'recentRecordings' => [] ]; // Get active timers $timersUrl = $baseUrl . '/emby/LiveTv/Timers?api_key=' . $this->config['embyToken']; try { $timersResponse = Requests::get($timersUrl, [], $options); if ($timersResponse->success) { $timers = json_decode($timersResponse->body, true); $stats['activeTimers'] = count($timers['Items'] ?? []); } } catch (Exception $e) { $this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get timers: ' . $e->getMessage()); } // Get series timers $seriesTimersUrl = $baseUrl . '/emby/LiveTv/SeriesTimers?api_key=' . $this->config['embyToken']; try { $seriesTimersResponse = Requests::get($seriesTimersUrl, [], $options); if ($seriesTimersResponse->success) { $seriesTimers = json_decode($seriesTimersResponse->body, true); $stats['seriesTimers'] = count($seriesTimers['Items'] ?? []); } } catch (Exception $e) { $this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get series timers: ' . $e->getMessage()); } // Get recordings from the last 90 days $recordingsUrl = $baseUrl . '/emby/LiveTv/Recordings?api_key=' . $this->config['embyToken'] . '&StartIndex=0&Limit=50&Fields=Overview,DateCreated&SortBy=DateCreated&SortOrder=Descending'; try { $recordingsResponse = Requests::get($recordingsUrl, [], $options); if ($recordingsResponse->success) { $recordings = json_decode($recordingsResponse->body, true); $allRecordings = $recordings['Items'] ?? []; // Count today's recordings $today = date('Y-m-d'); $todaysCount = 0; $recentRecordings = []; foreach ($allRecordings as $recording) { if (isset($recording['DateCreated'])) { $recordDate = date('Y-m-d', strtotime($recording['DateCreated'])); if ($recordDate === $today) { $todaysCount++; } // Add to recent recordings list $recentRecordings[] = [ 'date' => $recordDate, 'program' => $recording['Name'] ?? 'Unknown', 'series' => $recording['SeriesName'] ?? '', 'channel' => $recording['ChannelName'] ?? 'Unknown Channel', 'status' => 'Completed' ]; } } $stats['todaysRecordings'] = $todaysCount; $stats['totalRecordings'] = $recordings['TotalRecordCount'] ?? count($allRecordings); $stats['recentRecordings'] = array_slice($recentRecordings, 0, 10); // Limit to 10 recent recordings } } catch (Exception $e) { $this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get recordings: ' . $e->getMessage()); } $this->setAPIResponse('success', 'LiveTV stats retrieved successfully', 200, $stats); return true; } catch (Exception $e) { $this->setAPIResponse('error', 'Failed to retrieve LiveTV stats: ' . $e->getMessage(), 500); return false; } } public function getHomepageEmbyLiveTVActivity() { $debugEnabled = $this->config['homepageEmbyLiveTVTrackerDebug'] ?? false; if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'), true)) { if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->warning('Permission denied for user'); } $this->setAPIResponse('error', 'Permission denied', 403); return false; } if (!$this->config['embyURL'] || !$this->config['embyToken']) { $this->setAPIResponse('error', 'Emby URL or Token not configured', 500); return false; } try { if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->info('Activity method called - starting execution'); } $options = $this->requestOptions($this->config['embyURL'], null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']); $baseUrl = $this->qualifyURL($this->config['embyURL']); if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->info('Base URL configured: ' . $baseUrl); } $scheduledRecordings = []; $completedRecordings = []; $maxItems = intval($this->config['homepageEmbyLiveTVTrackerMaxItems'] ?? 10); $showCompleted = $this->config['homepageEmbyLiveTVTrackerShowCompleted'] ?? true; $maxCompletedItems = intval($this->config['homepageEmbyLiveTVTrackerMaxCompletedItems'] ?? 5); // Get user info if user info is enabled $userMap = []; $showUserInfo = $this->config['homepageEmbyLiveTVTrackerShowUserInfo'] ?? false; if ($showUserInfo) { $usersUrl = $baseUrl . '/emby/Users?api_key=' . $this->config['embyToken']; try { $usersResponse = Requests::get($usersUrl, [], $options); if ($usersResponse->success) { $users = json_decode($usersResponse->body, true); foreach ($users as $user) { $userMap[$user['Id']] = $user['Name']; } if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($userMap) . ' users for mapping'); } } } catch (Exception $e) { $this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get users: ' . $e->getMessage()); } } // Get scheduled recordings (active timers) $timersUrl = $baseUrl . '/emby/LiveTv/Timers?api_key=' . $this->config['embyToken'] . '&Fields=ChannelName,ChannelId,SeriesName,ProgramInfo,StartDate,EndDate,UserId'; if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->info('Fetching timers from URL: ' . $timersUrl); } try { $timersResponse = Requests::get($timersUrl, [], $options); if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API response status: ' . ($timersResponse->success ? 'success' : 'failed')); $this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API response code: ' . $timersResponse->status_code); } if ($timersResponse->success) { $timers = json_decode($timersResponse->body, true); $allTimers = $timers['Items'] ?? []; if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($allTimers) . ' timers from Emby API'); $this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API raw response length: ' . strlen($timersResponse->body)); $this->setLoggerChannel('EmbyLiveTVTracker')->info('First timer sample: ' . json_encode(array_slice($allTimers, 0, 1))); } // Sort timers by start date usort($allTimers, function($a, $b) { $aDate = $a['StartDate'] ?? ''; $bDate = $b['StartDate'] ?? ''; return strcmp($aDate, $bDate); }); $timersToProcess = array_slice($allTimers, 0, intval($maxItems)); if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->info('Processing ' . count($timersToProcess) . ' timers (maxItems: ' . $maxItems . ')'); $this->setLoggerChannel('EmbyLiveTVTracker')->info('All timers count before slice: ' . count($allTimers)); $this->setLoggerChannel('EmbyLiveTVTracker')->info('MaxItems value: ' . $maxItems . ' (type: ' . gettype($maxItems) . ')'); } foreach ($timersToProcess as $index => $timer) { if ($debugEnabled) { $this->setLoggerChannel('EmbyLiveTVTracker')->info('Processing timer ' . ($index + 1) . ': ' . json_encode([ 'Name' => $timer['Name'] ?? 'no name', 'StartDate' => $timer['StartDate'] ?? 'no start date', 'EndDate' => $timer['EndDate'] ?? 'no end date', 'ChannelName' => $timer['ChannelName'] ?? 'no channel name', 'Status' => $timer['Status'] ?? 'no status', 'UserId' => $timer['UserId'] ?? 'no user' ])); } // Calculate duration $duration = '-'; if (isset($timer['StartDate']) && isset($timer['EndDate'])) { $start = strtotime($timer['StartDate']); $end = strtotime($timer['EndDate']); if ($start && $end) { $minutes = round(($end - $start) / 60); $hours = floor($minutes / 60); $remainingMinutes = $minutes % 60; if ($hours > 0) { $duration = sprintf('%dh %dm', $hours, $remainingMinutes); } else { $duration = sprintf('%dm', $minutes); } } } // Get channel name - timers should have this information $channelName = $timer['ChannelName'] ?? null; if (empty($channelName) && !empty($timer['ChannelId'])) { $channelName = 'Channel ' . $timer['ChannelId']; } elseif (empty($channelName)) { $channelName = 'Unknown Channel'; } // Get user name $userName = null; if ($showUserInfo && !empty($timer['UserId']) && isset($userMap[$timer['UserId']])) { $userName = $userMap[$timer['UserId']]; } // Determine status based on timing $status = 'Scheduled'; $startTime = strtotime($timer['StartDate'] ?? ''); $endTime = strtotime($timer['EndDate'] ?? ''); $now = time(); if ($startTime && $endTime) { if ($now >= $startTime && $now <= $endTime) { $status = 'Recording'; } elseif ($now > $endTime) { $status = 'Completed'; } } // Get series name and episode title - try multiple approaches $seriesName = ''; $episodeTitle = ''; // Check for episode title first if (!empty($timer['ProgramInfo']['EpisodeTitle'])) { $episodeTitle = $timer['ProgramInfo']['EpisodeTitle']; } // Get series name if (!empty($timer['SeriesName'])) { $seriesName = $timer['SeriesName']; } elseif (!empty($timer['ProgramInfo']['SeriesName'])) { $seriesName = $timer['ProgramInfo']['SeriesName']; } elseif (!empty($timer['ProgramInfo']['Name'])) { $seriesName = $timer['ProgramInfo']['Name']; } elseif (!empty($timer['Name'])) { $seriesName = $timer['Name']; } // Use episode title if available, otherwise use program/series name $displayName = $episodeTitle ? $episodeTitle : ($timer['Name'] ?? ($timer['ProgramInfo']['Name'] ?? 'Unknown Program')); $activity = [ 'date' => $timer['StartDate'] ?? '', 'name' => $displayName, 'seriesName' => $seriesName, 'episodeTitle' => $episodeTitle, 'channelName' => $channelName, 'userName' => $userName, 'duration' => $duration, 'status' => $status, 'type' => 'timer' ]; $this->setLoggerChannel('EmbyLiveTVTracker')->info('Created activity for timer ' . ($index + 1) . ': ' . json_encode($activity)); // Debug timer date parsing $this->setLoggerChannel('EmbyLiveTVTracker')->info('Timer date debug - StartDate: "' . ($timer['StartDate'] ?? 'null') . '", parsed startTime: ' . ($startTime ? date('Y-m-d H:i:s', $startTime) : 'failed to parse') . ', EndDate: "' . ($timer['EndDate'] ?? 'null') . '", parsed endTime: ' . ($endTime ? date('Y-m-d H:i:s', $endTime) : 'failed to parse') . ', current time: ' . date('Y-m-d H:i:s', $now) . ', calculated status: ' . $status); $scheduledRecordings[] = $activity; } } } catch (Exception $e) { $this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get timers for activity: ' . $e->getMessage()); } // Get completed recordings if enabled if ($showCompleted) { $recordingsUrl = $baseUrl . '/emby/LiveTv/Recordings?api_key=' . $this->config['embyToken'] . '&StartIndex=0&Limit=' . $maxCompletedItems . '&Fields=DateCreated,SeriesName,RunTimeTicks&SortBy=DateCreated&SortOrder=Descending'; $this->setLoggerChannel('EmbyLiveTVTracker')->info('Fetching completed recordings from URL: ' . $recordingsUrl); try { $recordingsResponse = Requests::get($recordingsUrl, [], $options); if ($recordingsResponse->success) { $recordings = json_decode($recordingsResponse->body, true); $allRecordings = $recordings['Items'] ?? []; $this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($allRecordings) . ' completed recordings'); foreach ($allRecordings as $recording) { if (isset($recording['DateCreated'])) { // Format duration $duration = '-'; if (isset($recording['RunTimeTicks']) && $recording['RunTimeTicks'] > 0) { $minutes = floor($recording['RunTimeTicks'] / 600000000); $hours = floor($minutes / 60); $remainingMinutes = $minutes % 60; if ($hours > 0) { $duration = sprintf('%dh %dm', $hours, $remainingMinutes); } else { $duration = sprintf('%dm', $minutes); } } $completedRecordings[] = [ 'date' => $recording['DateCreated'], 'name' => $recording['Name'] ?? 'Unknown Program', 'seriesName' => $recording['SeriesName'] ?? '', 'channelName' => 'Unknown Channel', // Completed recordings don't have reliable channel info 'userName' => null, // No user info available for completed recordings 'duration' => $duration, 'status' => 'Completed', 'type' => 'recording' ]; } } } } catch (Exception $e) { $this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get completed recordings: ' . $e->getMessage()); } } $this->setLoggerChannel('EmbyLiveTVTracker')->info('Final scheduled recordings count: ' . count($scheduledRecordings)); $this->setLoggerChannel('EmbyLiveTVTracker')->info('Final completed recordings count: ' . count($completedRecordings)); $this->setLoggerChannel('EmbyLiveTVTracker')->info('Sample scheduled: ' . json_encode(array_slice($scheduledRecordings, 0, 1))); $this->setLoggerChannel('EmbyLiveTVTracker')->info('Sample completed: ' . json_encode(array_slice($completedRecordings, 0, 1))); $this->setAPIResponse('success', 'LiveTV activity retrieved successfully', 200, [ 'scheduledRecordings' => $scheduledRecordings, 'completedRecordings' => $completedRecordings ]); return true; } catch (Exception $e) { $this->setAPIResponse('error', 'Failed to retrieve LiveTV activity: ' . $e->getMessage(), 500); return false; } } } ?>