<?php

///////////////////////////////////////////////////////////////////////////////

require_once 'lib/tv.php';

///////////////////////////////////////////////////////////////////////////////
// Global constants

define('RET_LIVE_TEMPLATE_URL',			'ret_live_url');
define('RET_ARCHIVE_TEMPLATE_URL',		'ret_archive_url');

///////////////////////////////////////////////////////////////////////////////

class HomeTv implements Tv, UserInputHandler
{

	///////////////////////////////////////////////////////////////////////////
	// Constants

	const	ALL_CHANNELS_GROUP_ID				= '__home_tv_group_all_channels';
	const	FAV_CHANNELS_GROUP_ID				= '__home_tv_group_fav_channels';
	const	HDTV_CHANNELS_GROUP_ID				= '__home_tv_group_hdtv_channels';
	const	UHDTV_CHANNELS_GROUP_ID				= '__home_tv_group_uhdtv_channels';
	const	BIN_CHANNELS_GROUP_ID				= '__home_tv_group_bin_channels';
	const	RADIO_POINTS_GROUP_ID				= '__home_tv_group_radio_points';
	const	archive_delay_sec 					= 0;
	const	archive_past_sec 					= 259200;
	const	def_tv_channels_icons				= '/defaults/channels';
	const	dvb_channels_playlist_url			= '/tmp/dvb_channels.json';
	const	dvb_stickers_service_url			= 'https://brigadir.mydune.ru/home_tv/dvb/get_dvb_icon.php';

	///////////////////////////////////////////////////////////////////////////

	protected static $parsing_channels_ops =
		array
		(
			ControlSwitchDefs::switch_on	=> 'Группировать как субканалы в канал с меньшим номером',
			ControlSwitchDefs::switch_off	=> 'Включать все найденные каналы',
		);

	///////////////////////////////////////////////////////////////////////////

	private	$tv_mode;
	private $pf_enabled;
	private	$playback_runtime;
	private	$channel_deint_mode;
	private	$channel_zoom_preset;
	private	$plugin_cookies_epg;
	private	$plugin_cookies_location;
	private	$plugin_cookies_tv_archives;
	private	$plugin_cookies_playback_dvb;
	private	$plugin_cookies_playback_udp;
	private	$plugin_cookies_playback_http;
	private	$plugin_cookies_playlist_export;
	private	$plugin_cookies_customs_control;
	private	$fix_dune_zoom_deint_enabled;
	private $need_update_epf_mapping;
	private $channels_epg_ids;
	private $channels_icons;

	public	$plugin;
	public	$groups;
	public	$channels;
	public	$dvb_channels;
	public	$parental;
	public	$parental_passed;
	public	$dune_deint_mode;
	public	$dune_zoom_preset;
	public	$favorites_enabled;
	public	$parent_media_url_str;

	///////////////////////////////////////////////////////////////////////////

	private function get_ext_epg_init_action()
	{
		static $initialized = false;

		if ($initialized)
			return null;

		$action = ActionFactory::handle_user_input(
			array
			(
				'handler_id' => 'main',
				'control_id' => 'init',
			));
		$action[GuiAction::plugin_name] = 'ext_epg';
		$initialized = true;

		return $action;
	}

	private function load_dune_zoom_deint_values()
	{
		if ($system_settings = get_shell_settings())
		{
			$this->dune_zoom_preset = trim($system_settings['zoom_preset']);
			$this->dune_deint_mode = isset($system_settings['deinterlacing_mode'])? trim($system_settings['deinterlacing_mode']) : DuneParamsDeintMode::bob;
		}
		else
		{
			$this->dune_zoom_preset = DuneVideoZoomPresets::normal;
			$this->dune_deint_mode = DuneParamsDeintMode::bob;
		}
	}

	private function fix_dune_zoom_deint_values()
	{
		clearstatcache();

		if (file_exists('/tmp/settings.properties'))
		{
			$system_settings_runtime = parse_ini_file('/tmp/settings.properties', 0, INI_SCANNER_RAW);

			if (is_null($this->channel_zoom_preset) && is_null($this->channel_deint_mode))
			{
				if (isset($system_settings_runtime['zoom_preset']))
					$this->dune_zoom_preset = $system_settings_runtime['zoom_preset'];

				if (isset($system_settings_runtime['deinterlacing_mode']))
					$this->dune_deint_mode = $system_settings_runtime['deinterlacing_mode'];
			}
			else
			{
				if (!is_null($this->channel_zoom_preset))
				{
					$system_settings_runtime['zoom_preset'] = $this->dune_zoom_preset;
					$this->channel_zoom_preset = null;
				}

				if (!is_null($this->channel_deint_mode))
				{
					$system_settings_runtime['deinterlacing_mode'] = $this->dune_deint_mode;
					$this->channel_deint_mode = null;
				}

				$file = '';

				foreach($system_settings_runtime as $k => $v)
					$file .= "$k = $v" . PHP_EOL;

				file_put_contents('/tmp/settings.properties', $file);
			}
		}
	}

	private function download($url, $target)
	{
		if (!$hfile = fopen($target, "w"))
		{
			hd_print('Unable to download "' . $target . '"');
			return false;
		}

		$opts =
			array
			(
				CURLOPT_CONNECTTIMEOUT => 5,
				CURLOPT_TIMEOUT => 10,
				CURLOPT_RETURNTRANSFER => 0,
				CURLOPT_FOLLOWLOCATION => true,
				CURLOPT_USERAGENT => HTTP_USER_AGENT,
				CURLOPT_FILE => $hfile,
			);

		if (!http_get_document($url, $opts, SILENT_HTTP_REQUESTS))
		{
			fclose($hfile);
			unlink($target);
			return false;
		}

		return fflush($hfile) && fclose($hfile);
	}

	private function get_scan_opts_dialog_controls_defs(&$add_params)
	{
		ControlFactory::add_vgap($defs, 5);
		ControlFactory::add_multiline_label($defs, null, 'Выберите действие при обработке аналогичных каналов с разными номерами кнопок:', 10);
		ControlFactory::add_combobox($defs, $this, null, 'scan_opts_combo', null, $add_params['grouping_enabled'], self::$parsing_channels_ops, 1200, true);
		ControlFactory::add_vgap($defs, 40);

		if ($add_params['parsing_channels_opts_changed'])
			ControlFactory::add_button($button_defs, $this, null, 'save_scan_opts', null, 'Сохранить и запустить поиск', 650);
		else
			ControlFactory::add_button($button_defs, $this, null, 'save_scan_opts', null, 'Запустить поиск', 400);

		ControlFactoryExt::add_button_centered($defs, $button_defs, 1200);
		ControlFactory::add_vgap($defs, 20);

		return $defs;
	}

	private function get_scan_dialog_controls_defs(&$add_params)
	{
		ControlFactory::add_multiline_label($defs, null, rtrim($add_params['search_log_head'] . $add_params['search_log_tail'], "\n"), 10);
		ControlFactory::add_vgap($defs, ($add_params['supposed_lines_count'] - substr_count($add_params['search_log_head'] . $add_params['search_log_tail'], "\n")) * 44);

		if (!empty($add_params['channels_compare_log']))
		{
			ControlFactory::add_vgap($defs, -69);
			ControlFactory::add_smart_label($defs, null, '<text color=' . DEF_LABEL_TEXT_COLOR_GOLD . '>Обнаружены изменения в составе каналов!</text>');
			ControlFactory::add_vgap($defs, -18);
			ControlFactory::add_smart_label($defs, null, '<icon>' . PLUGIN_IMG_PATH . '/viewport.png</icon>');
			ControlFactory::add_vgap($defs, -56);
			ControlFactory::add_multiline_label($defs, null, $add_params['channels_compare_log'], 4);

			$lines = substr_count($add_params['channels_compare_log'], "\n");

			if ($lines > 4)
			{
				ControlFactory::add_smart_label($defs, null, '<gap width=670/><icon>' . PLUGIN_IMG_PATH . '/page_plus_btn.png</icon><gap width=10/><icon>' . PLUGIN_IMG_PATH . '/page_minus_btn.png</icon><text color=' . DEF_LABEL_TEXT_COLOR_SILVER . ' size=small>  Прокрутка</text>');
				ControlFactory::add_vgap($defs, -30);
			}
			else
				ControlFactory::add_vgap($defs, (4 - $lines) * 44);
		}

		ControlFactory::add_vgap($defs, 40);
		ControlFactory::add_close_dialog_and_apply_button($button_defs, $this, array('dialog_frame_style' => $add_params['dialog_frame_style'], 'mandratory_playback' => $add_params['mandratory_playback']), $add_params['post_action_id'], ($add_params['phase'] > 10)? 'OK' : 'Отмена', 300);
		ControlFactoryExt::add_button_centered($defs, $button_defs, 950);
		ControlFactory::add_vgap($defs, 10);

		return $defs;
	}

	private function do_update_scan_dialog_action(&$add_params, &$plugin_cookies)
	{
		static	$num_channels;
		static	$detected;
		static	$processed;
		static	$channels_keys;
		static	$groups_keys;
		static	$channels;
		static	$old_channels;
		static	$def_tv_icons;
		static	$packages;
		static	$location_info;
		static	$add_supposed_lines;

		switch ($add_params['phase'])
		{
			case 0:
				$channels_keys = null;
				$groups_keys = null;
				$channels = null;
				$old_channels = null;
				$def_tv_icons = null;
				$packages = null;
				$location_info = Wink()->get_location_info();
				$num_channels = 0;
				$processed = 0;
				$detected = 0;
				$add_supposed_lines = 0;
				$add_params['channels_compare_log'] = '';
				$add_params['phase'] = 1;
				break;

			case 1:
				// Search playlists ///////////////////////////////////////////
				hd_print('<... Search channels');
				$packages = Wink()->get_tv_packages();
				$detected = $packages? count($packages) : 0;
				$add_params['search_log_head'] .= " Найдено: {$detected}";
				$add_params['phase'] = empty($detected)? 5 : 2;
				///////////////////////////////////////////////////////////////
				break;

			case 2:
				$channels_keys = array_keys($packages);
				$add_params['search_log_head'] .= $add_params['search_log_tail'] . 'Сканирование плейлистов:';
				$add_params['phase'] = 3;
				break;

			case 3:
				if (file_exists(dirname(__FILE__) . '/www/channel_ids.txt'))
					unlink(dirname(__FILE__) . '/www/channel_ids.txt');

				$add_params['search_log_tail'] = " 0/{$detected}\nНайдено каналов: 0\n";
				$add_params['phase'] = 4;
				break;

			case 4:
				// Scan playlists /////////////////////////////////////////////
				$u_start = time();

				while(count($channels_keys) > 0)
				{
					$found = false;
					$pid = array_shift($channels_keys);

					if ($pid == $location_info->mrf)
						$found = Wink()->load_tv_channels($pid);

					$processed = $detected - count($channels_keys);

					if ($found !== false)
						hd_print('Loaded package "' . $packages[$pid] . '", found new channels: ' . $found);

					$add_params['search_log_tail'] = " $processed/$detected\nНайдено каналов: " . ($num_channels += $found) . "\n";
					break;
				}

				if ($processed == $detected)
				{
					if (!empty($num_channels))
					{
						$add_params['phase'] = 6;
						$channels = Wink()->get_tv_channels($add_params['grouping_enabled']);

						if (empty($channels['channels']))
						{
							$add_params['phase'] = 5;
							break;
						}

						$channels_keys = array_keys($channels['channels']);
						$groups_keys = array_keys($channels['groups']);
						$add_params['search_log_head'] .= " {$processed}/{$detected}\n";
						$add_params['search_log_tail'] = "Найдено каналов: {$num_channels}\n";
						$detected = count($channels_keys);
					}
					else
						$add_params['phase'] = 5;
				}
				///////////////////////////////////////////////////////////////
				break;

			case 5:
				// Channels not found! Out error! /////////////////////////////
				$add_params['search_log_head'] .= $add_params['search_log_tail'];
				$add_params['search_log_tail'] = "Поиск завершен с ошибкой:\n";

				if (empty($detected))
					$add_params['search_log_tail'] .= "\"Не найдено ни одного плейлиста\"\n";
				else if (empty($num_channels))
					$add_params['search_log_tail'] .= "\"Не найдено ни одного канала\"\n";

				unset($num_channels, $channels_keys, $groups_keys);
				$add_params['search_log_tail'] .= "Уточните настройки или повторите позже.\n";
				$add_params['phase'] = 11;
				///////////////////////////////////////////////////////////////
				break;

			case 6:
				// Grouping to subchannels ////////////////////////////////////
				hd_print('<... Caching channel icons...');
				$add_params['search_log_head'] .= $add_params['search_log_tail'];

				if ($add_params['grouping_enabled'])
					$add_params['search_log_head'] .= "Группировка в субканалы... Уникальных: {$detected}\n";

				$this->load_dvb_channels($plugin_cookies);
				$this->load_m3u((defined('DEBUG_MODE_ON') && file_exists('/D/ncdxml/wink-tv.m3u'))? '/D/ncdxml/wink-tv.m3u' : decode_str('X9aHR0cHM6Ly9icmlnYWRpci5teWR1bmUucnUvaG9tZV90di93aW5rL3dpbmsubTN1'));
				$m3u_backup_path = get_paved_path(defined('WINK_CACHE_STORAGE_PATH')? WINK_CACHE_STORAGE_PATH : LOCAL_CACHE_PATH . '/update') . '/wink.data';
				$add_params['m3u_channels_count'] = is_array($this->m3u_channels['names'])? count($this->m3u_channels['names']) : 0;

				if ($add_params['m3u_channels_count'] > 0)
					@file_put_contents($m3u_backup_path, serialize($this->m3u_channels));
				else if (file_exists($m3u_backup_path))
				{
					$this->m3u_channels = unserialize(file_get_contents($m3u_backup_path));
					$add_params['m3u_channels_count'] = is_array($this->m3u_channels['names'])? count($this->m3u_channels['names']) : 0;
				}

				if (file_exists(ICONS_CACHE_PATH . '/c160x115'))
				{
					$old_cache_dir = ICONS_CACHE_PATH . '/c160x115';
					$new_cache_dir = get_paved_path(ICONS_CACHE_PATH . '/channels');

					if ($handle = opendir($old_cache_dir))
					{
						while (false !== ($file = readdir($handle)))
							if (($file != ".") && ($file != ".."))
								rename($old_cache_dir . "/$file", $new_cache_dir . "/$file");

						closedir($handle);
					}

					rmdir($old_cache_dir);
				}

				$def_tv_icons = glob(PLUGIN_IMG_PATH . self::def_tv_channels_icons . '/*');
				$detected += count($groups_keys);
				$add_params['search_log_head'] .= 'Кэширование иконок:';
				$add_params['search_log_tail'] = " 0/{$detected}\n";
				$add_params['phase'] = 7;
				$processed = 0;
				///////////////////////////////////////////////////////////////
				break;

			case 7:
				// Caching icons //////////////////////////////////////////////
				if ($processed <> $detected)
				{
					$u_start = time();

					while($num = array_shift($channels_keys))
					{
						foreach($channels['channels'][$num]['sub'] as $id => $sub)
						{
							if ($logo = $this->download_icon($sub['logo'], ICONS_CACHE_PATH . '/channels', $sub['logo_hash']))
							{
								if (isset($channels['channels'][$num]['id']))
									if (isset($this->dvb_channels[$channels['channels'][$num]['id']]))
										$this->download_dvb_icon(ICONS_CACHE_PATH . "/channels/$logo", 'dvb');
							}
							else
							{
								$def_logo = $def_tv_icons[rand(0, count($def_tv_icons) - 1)];
								$logo = $this->download_icon($def_logo, ICONS_CACHE_PATH . '/channels', substr_replace(md5_file($def_logo), 0, 0, 1));
							}

							$channels['channels'][$num]['sub'][$id]['logo'] = $logo;
							$bc_name = preg_replace('/\s/', '', $sub['bcName']);
							$bc_name_sd = preg_replace('/(HD|UHD|4K)$/', '', $bc_name);

							unset($sub['logo_hash'], $channels['channels'][$num]['sub'][$id]['logo_hash']);

							if (isset($this->m3u_channels['names'][$bc_name]) || isset($this->m3u_channels['names'][$bc_name_sd]))
							{
								if (isset($this->m3u_channels['names'][$bc_name]))
									foreach ($this->m3u_channels['names'][$bc_name] as $url)
									{
										$bcid = md5("$num-$url");
										$sub['bcId'] = $bcid;
										$sub['logo'] = $logo;
										$sub['ottUrl'] = $url;
										$channels['channels'][$num]['sub'][$bcid] = $sub;
									}

								if (isset($this->m3u_channels['names'][$bc_name_sd]))
									foreach ($this->m3u_channels['names'][$bc_name_sd] as $url)
									{
										$bcid = md5("$num-$url");
										$sub['bcId'] = $bcid;
										$sub['bcName'] = preg_replace('/(HD|UHD|4K)$/', '', $sub['bcName']);
										$sub['logo'] = $logo;
										$sub['quality'] = 'sd';
										$sub['ottUrl'] = $url;
										$channels['channels'][$num]['sub'][$bcid] = $sub;
									}

								unset($channels['channels'][$num]['sub'][$id]);

								if (empty($channels['channels'][$num]['sub']))
									unset($channels['channels'][$num]);
							}
							else
								if (preg_match('/\/(CH_.*)\/manifest\.mpd/ui', $sub['ottUrl'], $matches))
									$uid = $matches[1];

							if (!empty($uid))
							{
								if (isset($this->m3u_channels['npvrs'][$uid]))
								{
									foreach ($this->m3u_channels['npvrs'][$uid] as $url)
										if (preg_match('/(\?.*token=.+)$/ui', $url, $matches))
										{
											$bcid = md5("$num-$url");
											$sub['bcId'] = $bcid;
											$sub['logo'] = $logo;
											$sub['ottUrl'] = $url;
											$channels['channels'][$num]['sub'][$bcid] = $sub;
										}

									unset($channels['channels'][$num]['sub'][$id]);

									if (empty($channels['channels'][$num]['sub']))
										unset($channels['channels'][$num]);
								}

								$uid = null;
							}
						}

						$processed = $detected - count($channels_keys);

						if ((time() - $u_start) >= 3)
							break;
					}

					while($idx = array_shift($groups_keys))
					{
						if (!$logo = $this->download_icon($channels['groups'][$idx]['logo'], ICONS_CACHE_PATH . '/groups', $channels['groups'][$idx]['logo_hash']))
						{
							$def_logo = ICONS_CACHE_PATH . '/defaults/groups/default_group.png';
							$logo = $this->download_icon($def_logo, ICONS_CACHE_PATH . '/groups', substr_replace(md5_file($def_logo), 0, 0, 1));
						}

						$channels['groups'][$idx]['logo'] = $logo;
						$processed = $detected - count($groups_keys);

						unset($channels['groups'][$idx]['logo_hash']);

						if ((time() - $u_start) >= 3)
							break;
					}

					$processed = ((count($channels_keys) + count($groups_keys)) == 0)? $detected : $processed;
					$add_params['search_log_tail'] = " {$processed}/{$detected}\n";
				}
				else
				{
					hd_print('Caching channels icons is completed! ...>');
					$add_params['phase'] = 8;
					unset($channels_keys, $groups_keys);
				}
				///////////////////////////////////////////////////////////////
				break;

			case 8:
				// Save playlist //////////////////////////////////////////////
				$add_params['search_log_head'] .= $add_params['search_log_tail'] . "Кэширование плейлиста...";
				$fplaylist = strval(time());
				$sub_location_id = $location_info->sub_location_id;

				if (!empty($channels['channels']))
				{
					if ($ofplaylist = Cache::get_latest_file(get_paved_path(PLAYLISTS_CACHE_PATH . "/tv/$sub_location_id")))
						$old_channels = unserialize(file_get_contents(get_paved_path(PLAYLISTS_CACHE_PATH . "/tv/$sub_location_id") . "/$ofplaylist"));

					ksort($channels['channels']);
					$status = @file_put_contents(get_paved_path(PLAYLISTS_CACHE_PATH . "/tv/$sub_location_id") . "/$fplaylist", serialize($channels));

					if ($status === false)
					{
						hd_print('Search abort with error: "Not saved playlist (no access to the repository)!" ->');
						$add_params['search_log_tail'] = " Fail\nПоиск завершен с ошибкой: \"Не удалось\nсохранить плейлист (нет доступа к хранилищу)\"\n";
						$add_params['phase'] = 11;
					}
					else
					{
						hd_print('Playlist "' . $sub_location_id . '/' . $fplaylist . '" saved successfully! ->');
						$this->plugin->user_data->fill_attributes('auto_subchannel_id', null);
						$this->plugin->user_data->save();

						if (file_exists(PLAYLISTS_CACHE_PATH . "/tv/$sub_location_id"))
							Cache::clean_dir(PLAYLISTS_CACHE_PATH . "/tv/$sub_location_id", $fplaylist);

						$add_params['search_log_tail'] = " OK\n";
						$add_params['phase'] = empty($this->plugin_cookies_playlist_export->path_m3u)? 10 : 9;
					}
				}
				else
				{
					hd_print('Search abort with error: "Not found channels!" ->');
					$add_params['search_log_tail'] = " Fail\nПоиск завершен с ошибкой: \"Не найдено\nни одного канала\"\n";
					$add_params['phase'] = 11;
				}

				unset($num_channels);
				///////////////////////////////////////////////////////////////
				break;

			case 9:
				// Export to M3U //////////////////////////////////////////////
				$add_params['search_log_head'] .= $add_params['search_log_tail'];
				$add_params['search_log_tail'] = '';
				$add_params['search_log_head'] .= "Экспорт плейлиста ";
				$local_storages = get_local_storages_list();
				$this->plugin_cookies_playlist_export->path_m3u = str_replace($local_storages['aliases'], $local_storages['names'], $this->plugin_cookies_playlist_export->path_m3u);

				if (file_exists(DUNE_MOUNTED_STORAGES_PATH . $this->plugin_cookies_playlist_export->path_m3u))
				{
					$list = "#EXTM3U\r\n";

					if (empty($this->groups[self::ALL_CHANNELS_GROUP_ID]['channels']))
					{
						$this->load_dvb_channels($plugin_cookies);
						$this->load_channels($plugin_cookies);
					}

					$tv_exclude_channel_ids = $this->get_exclude_channel_ids();

					foreach($this->groups[self::ALL_CHANNELS_GROUP_ID]['channels'] as $channel_id)
					{
						if (in_array($channel_id, $tv_exclude_channel_ids))
							continue;

						$url =  str_replace('?utcstart={b}&utcend={e}', '', $this->get_tv_playback_url($channel_id, 0, RET_LIVE_TEMPLATE_URL));

						if (!empty($url))
						{
							$channel = $this->get_channel($channel_id);
							$list .= '#EXTINF:0,' . $channel->get_title() . "\r\n$url\r\n";
						}
					}

					if (@file_put_contents(DUNE_MOUNTED_STORAGES_PATH . $this->plugin_cookies_playlist_export->path_m3u, $list) !== false)
						$add_params['search_log_tail'] = "выполнен успешно!\n";
					else
					{
						$add_params['search_log_tail'] = "завершен с ошибкой:\n\"Нет доступа к указанному хранилищу\"\n";
						$add_supposed_lines = 1;
					}
				}
				else
				{
					$add_params['search_log_tail'] = "завершен с ошибкой:\n\"Нет доступа к указанному файлу\"\n";
					$add_supposed_lines = 1;
				}
				///////////////////////////////////////////////////////////
				$add_params['phase'] = 10;
				break;

			case 10:
				// Compare playlists //////////////////////////////////////////
				if (!empty($old_channels))
				{
					foreach($old_channels['channels'] as $num => $data)
						if (isset($channels['channels'][$num]) && (($channels['channels'][$num]['id'] == $data['id']) || ($channels['channels'][$num]['title'] == $data['title'])))
							unset($old_channels['channels'][$num], $channels['channels'][$num]);

					if (!empty($old_channels['channels']))
					{
						$add_params['channels_compare_log'] .= ". Прекратили вещание:\n";

						foreach($old_channels['channels'] as $num => $data)
						{
							$hd_postfix = '';

							foreach($data['sub'] as $sub)
								if (($sub['quality'] <> 'sd') && ($sub['quality'] <> '4k'))
								{
									$hd_postfix = ' HD';
									break;
								}

							$add_params['channels_compare_log'] .= ".  $num. " . $data['title'] . $hd_postfix . "\n";
						}
					}

					if (!empty($channels['channels']))
					{
						$add_params['channels_compare_log'] .= ". Новые каналы:\n";

						foreach($channels['channels'] as $num => $data)
						{
							$hd_postfix = '';

							foreach($data['sub'] as $sub)
								if (($sub['quality'] <> 'sd') && ($sub['quality'] <> '4k'))
								{
									$hd_postfix = ' HD';
									break;
								}

							$add_params['channels_compare_log'] .= ".  $num. " . $data['title'] . $hd_postfix . "\n";
						}
					}

					if (empty($add_params['channels_compare_log']))
						$add_params['search_log_tail'] .= "Изменений в составе каналов не обнаружено!\n";
				}
				else
					$add_params['search_log_tail'] .= "Поиск каналов успешно завершен!\n";
				///////////////////////////////////////////////////////////////

				$add_params['supposed_lines_count'] += $add_supposed_lines;

			default:
				unset($channels, $old_channels);
				$add_params['phase'] = 11;
		}

		$params =
			array
			(
				'dialog_frame_style' => $add_params['dialog_frame_style'],
				'mandratory_playback' => $add_params['mandratory_playback'],
			);

		if (isset($add_params['initial_media_url']))
			$params['initial_media_url'] = $add_params['initial_media_url'];

		return
			UserInputHandlerRegistry::create_action($this, 'do_progress_scan', $params);
	}

	///////////////////////////////////////////////////////////////////////////

	public function __construct(&$plugin)
	{
		$this->plugin = $plugin;
		$this->tv_mode = TvModes::channels_n_to_m;

		if (!defined('GUI_EVENT_PLAYBACK_SWITCHED'))
			define('GUI_EVENT_PLAYBACK_SWITCHED', 'playback_switched');

		UserInputHandlerRegistry::get_instance()->register_handler($this);
	}

	public function get_channels()
	{
		return $this->channels;
	}

	public function get_channel($channel_id)
	{
		return
			empty($this->channels)? null : $this->channels->get($channel_id);
	}

	public function unload_channels()
	{
		$this->channels = null;
		$this->groups = null;
	}

	public function get_groups()
	{
		return $this->groups;
	}

	public function get_group($group_id)
	{
		return
			isset($this->groups[$group_id])? $this->groups[$group_id] : array();
	}

	public function get_input_pin_dialog_action(&$handle, $add_params = null)
	{
		ControlFactory::add_text_field($defs, $handle, $add_params, 'pin_field', 'Введите ПИН:', '', true, true, false, true, 500, true);
		ControlFactory::add_vgap($defs, 4);

		return
			ActionFactorySafe::show_dialog(null, $defs, false, 580, array(ShowDialogActionData::actions => array(GUI_EVENT_KEY_RETURN => UserInputHandlerRegistry::create_action($handle, 'input_pin_dialog_press_key_return')), 'dialog_params' => array('frame_style' => isset($add_params['dialog_frame_style'])? $add_params['dialog_frame_style'] : DIALOG_FRAME_STYLE_DEFAULT)));
	}

	public function usort_channels($sort_function)
	{
		return $this->channels->usort($sort_function);
	}

	public function get_handler_id()
	{
		return 'home_tv_handler';
	}

	public function get_fav_group_icon_url()
	{
		return 'plugin_file://img/defaults/groups/fav_group.png';
	}

	public function download_icon($from_url, $to_path, $hash_str)
	{
		if (empty($from_url) || empty($hash_str))
			return false;

		$to_path = get_paved_path($to_path);
		$icon = "$to_path/" . basename($from_url);
		$icon_info = pathinfo($from_url);
		$file = "$to_path/$hash_str.{$icon_info['extension']}";

		if (file_exists($file) && filesize($file) > 1000)
			return basename($file);

		$file_hash = $download_success = null;
		$op = file_exists($icon)? 'check_local_icon' : 'download_icon';

		while(!$download_success || !$file_hash)
		{
			switch($op)
			{
				case 'check_local_icon':
					if (rename($icon, $file))
						if (file_exists($file))
						{
							$op = 'check_local_hash';
							break;
						}

				case 'download_icon':
					$download_success = preg_match('/^http/i', $from_url)? $this->download($from_url, $file) : file_put_contents($file, file_get_contents($from_url));

					if ($download_success && file_exists($file))
					{
						$op = 'check_hash';
						break;
					}
					return false;

				case 'check_local_hash':
					$check_local_hash = true;

				case 'check_hash':
					$file_hash = substr_replace(md5_file($file), 0, 0, 1);

					if (!empty($file_hash))
					{
						for ($i = 1; $i <= $hash_str[0]; $i++)
							$file_hash = substr_replace(md5($file_hash), $i, 0, 1);

						if ($file_hash == $hash_str)
							return basename($file);

						unlink($file);

						if (!empty($check_local_hash))
						{
							$op = 'download_icon';
							$file_hash = null;
							break;
						}
					}
					return false;
			}
		}
	}

	public function download_dvb_icon($orig_icon_path, $cut = '')
	{
		if (empty($orig_icon_path))
			return false;

		$path = pathinfo($orig_icon_path);
		$path_to_save = $path['dirname'] . '/';
		$target = $path['basename'];

		if (!empty($cut))
		{
			$cut = rtrim($cut, '/') . '/';
			$path_to_save .= $cut;
			if (!file_exists($path_to_save))
				@mkdir($path_to_save, 0777, true);
		}

		if (file_exists($path_to_save . $target))
			return true;

		if (!$hfile = fopen($path_to_save . $target, "w"))
		{
			hd_print('Unable to download "' . $target . '"');
			return false;
		}

		$opts =
			array
			(
				CURLOPT_CONNECTTIMEOUT => 5,
				CURLOPT_TIMEOUT => 10,
				CURLOPT_RETURNTRANSFER => false,
				CURLOPT_FOLLOWLOCATION => true,
				CURLOPT_FILE => $hfile,
			);

		if (!http_post_document(self::dvb_stickers_service_url, array('file' => "@{$orig_icon_path}"), $opts, SILENT_HTTP_REQUESTS))
		{
			fclose($hfile);
			unlink($target);
			return false;
		}

		return fflush($hfile) && fclose($hfile);
	}

	public function load_m3u($url)
	{
		$this->m3u_channels = $m3u_channels = array();

		if (!empty($url))
		{
			$m3u = file_get_contents($url);
			$playlist = explode('#EXTINF', $m3u);

			foreach($playlist as $channels)
			{
				if (strpos($channels, '#EXTM3U') === false)
				{
					$lines = explode("\n", $channels);

					foreach($lines as $line)
					{
						if (strpos($line, ':') === 0)
						{
							$ch_name = preg_match('/,\s*(.*)$/u', str_ireplace(array('!', 'Пятый канал', ' '), array('', 'Петербург – 5 канал', ''), $line), $matches)? rtrim($matches[1]) : "channel_$i";
							continue;
						}
						else
							if (!empty($ch_name))
							{
								$url = rtrim(trim($line), '=');
								$ch_npvr = preg_match('/\/(CH_.*)\//uU', $url, $matches)? rtrim($matches[1]) : "";

								if (!empty($ch_npvr) && !empty($url))
									if (strpos($url, '@') !== 0)
									{
										$m3u_channels['npvrs'][$ch_npvr][] = $url;
										$m3u_channels['names'][$ch_name][] = $url;
									}
							}
					}
				}
			}
		}

		if (empty($m3u_channels))
			return false;

		$this->m3u_channels = $m3u_channels;
		return true;
	}

	public function change_tv_favorites($fav_op_type, $channel_id, &$plugin_cookies)
	{
		$fav_channel_ids = $this->get_fav_channel_ids($plugin_cookies);

		if ($fav_op_type === PLUGIN_FAVORITES_OP_ADD)
			array_push($fav_channel_ids, $channel_id);
		else
		{
			$k = array_search($channel_id, $fav_channel_ids);

			if ($k === false)
				return null;

			if ($fav_op_type === PLUGIN_FAVORITES_OP_REMOVE)
				unset ($fav_channel_ids[$k]);
			else if (($fav_op_type === PLUGIN_FAVORITES_OP_MOVE_UP) && !empty($k))
			{
				$t = $fav_channel_ids[$k - 1];
				$fav_channel_ids[$k - 1] = $fav_channel_ids[$k];
				$fav_channel_ids[$k] = $t;
			}
			else if (($fav_op_type === PLUGIN_FAVORITES_OP_MOVE_DOWN) && ($k != count($fav_channel_ids) - 1))
			{
				$k = array_search($channel_id, $fav_channel_ids);

				$t = $fav_channel_ids[$k + 1];
				$fav_channel_ids[$k + 1] = $fav_channel_ids[$k];
				$fav_channel_ids[$k] = $t;
			}
		}

		$this->set_fav_channel_ids($fav_channel_ids, $plugin_cookies);
		$this->plugin->epg->set_tv_favorites($fav_channel_ids);
		$this->need_update_epf_mapping = true;
		$media_urls = array(TvFavoritesScreen::get_media_url_str(), TvChannelsScreen::get_media_url_str(self::ALL_CHANNELS_GROUP_ID));

		foreach($this->groups as $group_id => $group)
			if (in_array($channel_id, $group['channels']))
				$media_urls[] = TvChannelsScreen::get_media_url_str($group_id);

		if (NEWGUI_FEAUTURES_AVAILABLE)
		{
			EpfsHandler::refresh_tv_epfs($plugin_cookies);

			return
				EpfsHandler::invalidate_folders($media_urls);
		}

		return
			ActionFactorySafe::invalidate_folders($media_urls);
	}

	public function get_http_url($template_url, $npvr_id, $return_tstv = false, $archive_ts = 0)
	{
		# $archive_ts = 0 - Return template NPVR URL
		# $archive_ts < 0 - Return Live URL
		# $archive_ts > 0 - Return TV Archive URL

		if (empty($template_url) && empty($npvr_id))
			return null;

		if (empty($template_url) && !empty($npvr_id))
			$media_url = null;
		else
			$media_url = $template_url;

		if ($archive_ts >= 0)
		{
			$media_url_is_http_wink = preg_match('/\/(hls|mdrm)\/(CH_|\d+\.).*t\.(m3u8|mpd)/iu', $media_url);
			$media_url_is_http_4duk = preg_match('/m3u8\?token=4duk/iu', $media_url);

			if (($media_url == $template_url) && ($media_url_is_http_wink || $media_url_is_http_4duk))
				if (extract(parse_url($media_url)))
				{
					$port = empty($port)? '' : ":$port";
					$media_url = "$scheme://$host$port$path";

					if (!empty($query))
						foreach(explode('&', $query) as $param)
							if (preg_match('/(.+)=(.*$)/U', $param, $matches))
								$params[$matches[1]] = $matches[2];

					if ($return_tstv)
					{
						$params['offset'] = '{-o}';

						if ($media_url_is_http_4duk)
							$params['utcstart'] = '{b}';
					}
					else
					{
						$params['utcstart'] = '{b}';
						$params['utcend'] = '{e}';
					}

					$query = '';

					foreach($params as $name => $value)
						$query .= "$name=$value&";

					$media_url = rtrim("$media_url?$query", '?&');
				}

			if (empty($archive_ts))
				return $media_url;

			$now_ts = time();
			$begin_ts = $archive_ts;
			$offset_sec = $begin_ts - $now_ts;
			$end_ts = (($now_ts - $begin_ts) > 14359)? $begin_ts + 14399 : $now_ts;
			$media_url = str_replace(array('{t}', '{b}', '{e}', '{o}', '{-o}'), array($now_ts, $begin_ts, $end_ts, -$offset_sec, $offset_sec), $media_url);
		}
		else
			return
				preg_match('/(^http.*:\/\/)|(^udp:\/\/)|(\/udp\/)|(^dvb:\/\/)/', $media_url)? $media_url : null;

		return
			preg_match('/http.*:\/\//', $media_url)? $media_url : null;
	}

	public function get_tv_playback_url($channel_id, $archive_ts, $op_code, &$plugin_cookies = null)
	{
		if (empty($op_code))
			PlaybackPoints::push($channel_id, $archive_ts);

		$this->fix_dune_zoom_deint_enabled = false;
		$this->plugin->user_data->select($channel_id);
		$channel = $this->get_channel($channel_id);
		$next_subchannel_id = null;
		$connect_attempts = 10;

		while($connect_attempts > 0)
		{
			if (is_null($channel_ott_url = $this->plugin->user_data->live_streaming_url))
				if ($this->plugin_cookies_playback_http->hls_bs_prefer == HttpStreamsDefs::mpd_abs)
					$channel_ott_url = $channel->get_ott_mpd_url();

			if (empty($channel_ott_url))
				$channel_ott_url = $channel->get_ott_hls_url();

			$ott_url_is_http = (stripos(trim($channel_ott_url), 'http') === 0);
			$ott_url_is_mpd = $ott_url_is_http && (stripos($channel_ott_url, '.mpd') !== false);
			$ott_url_is_4duk = $ott_url_is_http && preg_match('/m3u8\?token=4duk.*?/ui', $channel_ott_url);
			$ott_url_is_dmi3y = $ott_url_is_http && preg_match('/dmi3y-tv\.ru/ui', $channel_ott_url);
			$media_url = null;

			if ($ott_url_is_dmi3y)
			{
				$curl_opts = array
					(
						CURLOPT_CONNECTTIMEOUT => 10,
						CURLOPT_TIMEOUT => 10,
						CURLOPT_FOLLOWLOCATION => true,
						CURLOPT_SSL_VERIFYPEER => false,
						CURLOPT_USERAGENT => HTTP_USER_AGENT
					);

				if ($m3u8 = http_get_document($channel_ott_url, $curl_opts, SILENT_HTTP_REQUESTS))
					if (preg_match('/^http.+$/mi', $m3u8, $matches))
						$channel_ott_url = preg_replace(array('/http:\/\/.+-/Ui', '/playlist\.m3u8.*$/ui'), array('http://', 'variant.m3u8'), $matches[0]);
			}

			if ($ott_url_is_mpd)
			{
				if (extract(parse_url($channel_ott_url)) && !empty($query))
				{
					$params = '?';

					foreach(explode('&', $query) as $param)
						if (preg_match('/token=/', $param))
							$token = $param;
						else
							$params .= "$param&";

					$channel_ott_url = rtrim("$scheme://$host$path$params", '?&');
				}
			}

			$npvr_id = ($this->plugin_cookies_playback_http->hls_bs_prefer == HttpStreamsDefs::hls_abs)? null : $channel->get_npvr_id();
			$ret_live_url = ($op_code == RET_LIVE_TEMPLATE_URL);
			$ret_archive_url = ($op_code == RET_ARCHIVE_TEMPLATE_URL);

			if ($ret_live_url || $ret_archive_url)
				$archive_ts = 0;

			if (($archive_ts > 0) || $ret_archive_url)
			{
				if (($this->plugin_cookies_tv_archives->enabled <> ControlSwitchDefs::switch_on) || ($this->plugin->user_data->tv_archive_url == 'empty'))
					return null;

				$fallback_sec = $ott_url_is_4duk? 14400 : 3600;
				$return_tstv = (($this->plugin_cookies_tv_archives->playback_stream_type == TvArchivePlaybackDefs::tstv) || (($this->plugin_cookies_tv_archives->playback_stream_type == TvArchivePlaybackDefs::auto) && ((time() - $archive_ts) <= $fallback_sec)));
				$media_url = ($this->plugin_cookies_playback_http->enabled == ControlSwitchDefs::switch_on)? $this->get_http_url($channel_ott_url, $npvr_id, $return_tstv, $archive_ts) : null;

				if ($ret_archive_url)
					return
						empty($media_url)? null : $media_url;
			}
			else
			{
				if ($ott_url_is_dmi3y && !empty($channel_ott_url))
					$media_url = $channel_ott_url;
				else
					$media_url = $this->plugin->user_data->live_streaming_url;

				if (empty($media_url))
				{
					$playback_dvb_enabled = DVB_PLAYBACK_AVAILABLE && ($this->plugin_cookies_playback_dvb->enabled == ControlSwitchDefs::switch_on) && !empty($this->dvb_channels);
					$media_url = $channel->get_streaming_url($playback_dvb_enabled);

					if (!empty($media_url))
						if (((stripos($media_url, 'dvb://') !== false) && !$playback_dvb_enabled) ||
							((stripos($media_url, 'udp://@') !== false) && ($this->plugin_cookies_playback_udp->enabled <> ControlSwitchDefs::switch_on)))
								$media_url = null;

					if (is_numeric(str_ireplace(array('udp://@', '.', ':'), '', $media_url)) && ($this->plugin_cookies_playback_udp->udpxy->enabled == ControlSwitchDefs::switch_on))
						$media_url = str_ireplace('udp://@', 'http://ts://' . $this->plugin_cookies_playback_udp->udpxy->ip . ':' . $this->plugin_cookies_playback_udp->udpxy->port . '/udp/', $media_url);
				}

				if ($media_url == 'empty')
					$media_url = '';
				else if ($this->plugin_cookies_playback_http->enabled == ControlSwitchDefs::switch_on)
				{
					if (empty($media_url))
						$media_url = $this->get_http_url($channel_ott_url, $npvr_id, false, $archive_ts);
					else
						$media_url = $this->get_http_url($media_url, $npvr_id, false, -1);
				}

				if ($ret_live_url)
					return
						empty($media_url)? null : $media_url;
			}

			if (empty($media_url))
				return null;

			$dune_params = array();

			if (preg_match('/(.*)\|\|\|dune_params\|\|\|(.*)/', $media_url, $matches))
			{
				$media_url = $matches[1];

				foreach (explode(',', $matches[2]) as $param)
					if (preg_match('/(.*):(.*)/', $param, $matches))
						$dune_params[$matches[1]] = $matches[2];
			}

			$this->channel_zoom_preset = $this->plugin->user_data->zoom;
			$this->channel_deint_mode = $this->plugin->user_data->deint;

			if (!isset($dune_params['zoom']))
				$dune_params['zoom'] = is_null($this->channel_zoom_preset)? $this->dune_zoom_preset : $this->channel_zoom_preset;

			if (!isset($dune_params['deint_mode']))
				$dune_params['deint_mode'] = str_replace(array('0', '1', '4', '5'), array('bob', 'off', 'adaptive', 'adaptive_plus'), is_null($this->channel_deint_mode)? $this->dune_deint_mode : $this->channel_deint_mode);

			$media_url_is_http = (stripos(trim($media_url), 'http') === 0);
			$media_url_is_mpd = $media_url_is_http && (stripos($media_url, '.mpd') !== false);
			$media_url_is_hls = $media_url_is_http && (stripos($media_url, '.m3u8') !== false);
			$media_url_is_zabava = $media_url_is_hls && preg_match('/(zabava|s\d+).*cdn\.ngenix\.net/i', $media_url);

			if ($media_url_is_http)
			{
				$curl_opts = array
					(
						CURLOPT_CONNECTTIMEOUT => 10,
						CURLOPT_TIMEOUT => 10,
						CURLOPT_FOLLOWLOCATION => true,
						CURLOPT_SSL_VERIFYPEER => false,
						CURLOPT_USERAGENT => HTTP_USER_AGENT
					);

				if ($this->plugin_cookies_playback_http->secury_disable <> ControlSwitchDefs::switch_off)
					$media_url = preg_replace('/https:\/\//', 'http://', $media_url);

				if ($media_url_is_hls)
				{
					if ($media_url_is_zabava)
					{
						try
						{
							if (!$m3u8 = http_get_document($media_url, $curl_opts, SILENT_HTTP_REQUESTS))
								throw new Exception('HTTP error!');

							$lines = explode("\n", str_replace("\r", '', $m3u8));

							foreach($lines as $idx => $str)
							{
								if (strpos($str, '#EXT-X-STREAM-INF') === 0)
								{
									if (preg_match_all('/RESOLUTION=(\d+).*,/', $str, $matches) && isset($lines[$idx+1]))
										$playlist[intval($matches[1][0])] = $lines[$idx+1];
								}
							}

							if (isset($playlist))
							{
								krsort($playlist);

								foreach($playlist as $hres => $media_url)
								{
									if ($plugin_cookies->qfilter == QualityDefs::highest)
									{
										if (preg_match('/android|87../', get_platform_kind())) break;
										if ($hres <= 1920) break;
									}
									if (($plugin_cookies->qfilter == QualityDefs::middle) && ($hres <= 1280)) break;
									if (($plugin_cookies->qfilter == QualityDefs::lowest) && ($hres <= 720)) break;
								}
							}

							if (!empty($next_subchannel_id))
							{
								$this->plugin->user_data->auto_subchannel_id = $next_subchannel_id;
								$this->plugin->user_data->save();
							}
						}
						catch (Exception $e)
						{
							$media_url = null;
							$connect_attempts = isset($connect_attempts) ? $connect_attempts : 10;
							$connect_attempts -= 1;
							$next_subchannel_id = $channel->change_next_subchannel();
							continue;
						}

						if ((DUNE_FIRMWARE_BN < 11)/* || ($this->plugin_cookies_playback_http->secury_disable <> ControlSwitchDefs::switch_on)*/)
							$media_url = PLUGIN_CGI_URL . "repeater?$media_url";
						else
							$dune_params['http_headers'] = rawurlencode('User-Agent: ' . HTTP_USER_AGENT);
					}
					else
						$dune_params['http_headers'] = rawurlencode('User-Agent: ' . HTTP_USER_AGENT);

					if (($archive_ts <= 0) || (($archive_ts > 0) && ($this->plugin_cookies_tv_archives->reload_when_resume <> ControlSwitchDefs::switch_on)))
						$dune_params['hls_forced_type'] = 'event';
				}

				if ($media_url_is_mpd)
				{
					if (!empty($token))
					{
						$dune_params['decryption_key'] = substr(decode_str($token, 8), 0, 32);
						$media_url = rtrim(str_replace($token, '', $media_url), '?& ');
					}

					try
					{
						if (!$mpd = http_get_document($media_url, $curl_opts, SILENT_HTTP_REQUESTS))
							throw new Exception('HTTP error!');

						if ($xml = simplexml_load_string($mpd, null, LIBXML_NOCDATA | LIBXML_NOEMPTYTAG))
						{
							$base_url = $xml->BaseURL;

							foreach($xml->Period->AdaptationSet[0]->Representation as $variant)
								$playlist[(integer) $variant['width']] = (string) $variant['winkId'];

							if (isset($playlist))
							{
								krsort($playlist);

								foreach($playlist as $hres => $wid)
								{
									if ($plugin_cookies->qfilter == QualityDefs::highest)
									{
										if (preg_match('/android|87../', get_platform_kind())) break;
										if ($hres <= 1920) break;
									}
									if (($plugin_cookies->qfilter == QualityDefs::middle) && ($hres <= 1280)) break;
									if (($plugin_cookies->qfilter == QualityDefs::lowest) && ($hres <= 800)) break;
								}

								$media_url = str_replace('manifest.mpd', "$wid/manifest.mpd", $base_url);
							}

							if (!empty($next_subchannel_id))
							{
								$this->plugin->user_data->auto_subchannel_id = $next_subchannel_id;
								$this->plugin->user_data->save();
							}
						}
					}
					catch (Exception $e)
					{
						$media_url = null;
						$connect_attempts = isset($connect_attempts) ? $connect_attempts : 10;
						$connect_attempts -= 1;
						$next_subchannel_id = $channel->change_next_subchannel();
						continue;
					}

					$dune_params['http_headers'] = rawurlencode('User-Agent: ' . HTTP_USER_AGENT);
				}

				if (!isset($dune_params['buffering_ms']))
					$dune_params['buffering_ms'] = ($this->plugin_cookies_playback_udp->udpxy->enabled == ControlSwitchDefs::switch_on)? $this->plugin_cookies_playback_udp->udpxy->buff_size_ms : $this->plugin_cookies_playback_http->buff_size_ms;
			}

			break;
		}

		$dune_params_str = '|||dune_params|||';

		foreach($dune_params as $k => $v)
			$dune_params_str .= $k . ':' . $v . ',';

		if ($media_url_is_zabava)
			$media_url .= $dune_params_str . 'vmx_drm:1';
		else
			$media_url = (((stripos($media_url, 'dvb://') !== false) || $media_url_is_http)? $media_url : 'drm://' . $this->plugin->app_name . '/' . $this->plugin->app_drm_id . '/' . $media_url) . rtrim($dune_params_str, ',');

		hd_print('Change playback URL to "' . $this->plugin->custom_epg->retrive_tv_playback_url($channel_id, $archive_ts, preg_match('/play.megafon.*\/(hls\/CH_.*)\//i', $media_url, $matches)? preg_replace('/\/(hls\/.*)\//', '/' . rtrim(base64_encode($matches[1]), '=') . '/', $media_url) : $media_url) . '"');

		return preg_replace('/profile=drmd/', '', $media_url);
	}

	////////////////////////////////////////////////////////////////////////////

	public function ensure_channels_loaded(&$plugin_cookies)
	{
		if (empty($this->channels) || ($this->channels->size() == 0))
		{
			$this->load_dvb_channels($plugin_cookies);
			$this->load_channels($plugin_cookies);
		}

		$this->need_update_epf_mapping = false;
	}

	public function get_epg_providers(&$plugin_cookies)
	{
		$parsers = $this->plugin->epg->get_parsers_list();
		$plugin_cookies_epg = json_decode($plugin_cookies->epg);

		return
			array_values(array_unique(array_merge($plugin_cookies_epg->providers, $parsers)));
	}

	public function load_dvb_channels(&$plugin_cookies)
	{
		$this->dvb_channels = array();
		$this->plugin_cookies_playback_dvb = json_decode($plugin_cookies->playback_dvb);

		if (DVB_PLAYBACK_AVAILABLE && ($this->plugin_cookies_playback_dvb->enabled == ControlSwitchDefs::switch_on) && file_exists(self::dvb_channels_playlist_url))
		{
			if (file_exists(self::dvb_channels_playlist_url))
			{
				hd_print('Loading DVB channels...');

				if (!is_null($channels = json_decode(file_get_contents(self::dvb_channels_playlist_url))) && !empty($channels->channels))
				{
					$date = date_create_from_format("Y-m-d?G:i:s.uP", $channels->generation_date);
					hd_print('Date of last scan channels "' . $date->format('Y-m-d H:i:s') . '"');
					hd_print('Found ' . count($channels->channels) . ' channels');

					foreach($channels->channels as $channel)
					{
						if ((strpos($channel->channelId, 'RADIO:') !== false))
							continue;

						$channel_title = trim(preg_replace(array('/\s+(([S|H]D)|([L|H]Q)|4K)\s*$/', '/!|\.|\*|"|«|»|\s*_.*/', '/\s+and\s+/i', '/(\s*-\s*)/', '/(\s*\+\s*)/'), array('', '', ' & ', '-', '&'), $channel->name));
						$channel_title_id = get_canonize_string(preg_replace('/\s/', '', get_canonize_string($channel_title)));

						if (preg_match('/(Ultra\s*HD|((^|\s+|\()(4K|UHD)(\).*|(\s*)|\s+.*))$)/', $channel->name))
							$resolution = '4k';
						else if (preg_match('/(^\d+HD|\s+HD)(\s.*|)$/', $channel->name))
							$resolution = 'hd';
						else
							$resolution = 'sd';

						$this->dvb_channels[$channel_title_id][$channel->id] =
							array
							(
								'lcn' => $channel->lcn,
								'res' => $resolution,
								'name' => $channel->name,
								'url' => $channel->playbackUrl,
							);
					}
					hd_print('DVB channels loaded successfully!');
				}
				else
					hd_print('DVB channels loading aborted, playlist is empty!');
			}
		}
	}

	public function load_channels(&$plugin_cookies)
	{
		$t = microtime(1);

		if ($plugin_cookies->app_version <> $this->plugin->app_version)
			$plugin_cookies->app_version = $this->plugin->app_version;

		if (file_exists('/tmp/home_tv_was_reloaded_mark'))
			unlink('/tmp/home_tv_was_reloaded_mark');

		if (file_exists('/tmp/home_tv_was_installed_mark'))
			unlink('/tmp/home_tv_was_installed_mark');

		if (file_exists('/config/home_tv_was_installed_mark'))
			unlink('/config/home_tv_was_installed_mark');

		$this->groups = array();
		$this->channels = new MappedArray();
		$this->plugin->epg->tv_info_set_faulty(1);
		$this->parental = json_decode($plugin_cookies->parental);
		$this->plugin_cookies_tv_archives = json_decode($plugin_cookies->tv_archives);
		$this->plugin_cookies_customs_control = json_decode($plugin_cookies->customs_control);
		$this->favorites_enabled = ($plugin_cookies->favorites_enable == ControlSwitchDefs::switch_on);
		$this->plugin_cookies_playback_udp = json_decode($plugin_cookies->playback_udp);
		$this->plugin_cookies_playback_http = json_decode($plugin_cookies->playback_http);
		$this->plugin_cookies_location = json_decode($plugin_cookies->location);
		Wink()->set_location($this->plugin_cookies_location->id);
		$this->load_dune_zoom_deint_values();
		$this->plugin->user_data->load(USER_DATA_CACHE_PATH . "/{$this->plugin_cookies_location->id}");

		if ($this->plugin_cookies_playback_udp->udpxy->enabled == ControlSwitchDefs::switch_on)
		{
			$ip = trim($this->plugin_cookies_playback_udp->udpxy->ip);
			$port = trim($this->plugin_cookies_playback_udp->udpxy->port);

			if (empty($ip))
				$this->plugin_cookies_playback_udp->udpxy->enabled = ControlSwitchDefs::switch_off;
			else if (empty($port))
				$this->plugin_cookies_playback_udp->udpxy->port = '80';
		}

		if ($this->favorites_enabled)
			$this->groups[self::FAV_CHANNELS_GROUP_ID] =
				array
				(
					'title' => get_system_language_string_value('dune_tv_favorites'),
					'is_adult' => false,
					'icon_url' => $this->get_fav_group_icon_url(),
					'channels' => array(),
				);

		$this->groups[self::ALL_CHANNELS_GROUP_ID] =
			array
			(
				'title' => get_system_language_string_value('dune_tv_all_channels'),
				'is_adult' => false,
				'icon_url' => 'plugin_file://img/defaults/groups/all_group.png',
				'channels' => array(),
			);

		$hd_genre_id = null;
		$uhd_genre_id = null;
		$path_to_playlist = get_paved_path(PLAYLISTS_CACHE_PATH . "/tv/{$this->plugin_cookies_location->id}");
		$playlist_fname = Cache::get_latest_file($path_to_playlist);

		if (!empty($playlist_fname))
		{
			$playlist_date = date('Y-m-d H:i:s', $playlist_fname);

			if ($plugin_cookies->playlist_date <> $playlist_date)
				$plugin_cookies->playlist_date = $playlist_date;

			hd_print('Tv playlist found in cache: "' . $playlist_fname . '"');
			hd_print("Playlist generation date: $playlist_date");
			$playlist = unserialize(file_get_contents("$path_to_playlist/$playlist_fname"));
		}
		else
		{
			hd_print('Tv channels loading aborted, playlist not found in cache!');
			return 0;
		}

		hd_print('Loading tv channels from playlist...');

		if (!empty($playlist['groups']))
		{
			ksort($playlist['groups']);

			if (file_exists(ICONS_CACHE_PATH . '/groups'))
			{
				$icons_cache = ICONS_CACHE_PATH . '/groups/';
				$icon_key = 'logo';
			}
			else
			{
				$icons_cache = PLUGIN_IMG_PATH . '/defaults/groups/';
				$icon_key = 'icon';
			}

			foreach ($playlist['groups'] as $group_id => $group_data)
			{
				if (preg_match('/u.*hd/i', $group_data['name']))
				{
					$uhd_genre_id = $group_id;
					$group_id = str_replace('__home_tv_group_', '', self::UHDTV_CHANNELS_GROUP_ID);
				}
				else if (preg_match('/^\s*hd\s*$/i', $group_data['name']))
				{
					$hd_genre_id = $group_id;
					$group_id = str_replace('__home_tv_group_', '', self::HDTV_CHANNELS_GROUP_ID);
				}

				$this->groups['__home_tv_group_' . $group_id] =
					array
					(
						'title' => $group_data['name'],
						'icon_url' => $icons_cache . $group_data[$icon_key],
						'is_adult' => preg_match('/взрослы/iu', $group_data['name']),
						'channels' => array(),
					);
			}
		}

		if (!empty($playlist['channels']))
		{
			foreach ($playlist['channels'] as $number => $data)
			{
				$channel =
					new TvChannel(
						$data['id'],
						$number,
						$data['title'],
						$data['desc'],
						$plugin_cookies->qfilter,
						$this->plugin_cookies_playback_http->hls_bs_prefer,
						$data['sub'],
						isset($this->dvb_channels[$data['id']])? $this->dvb_channels[$data['id']] : null,
						$this->plugin->user_data);

				$channel_id = $channel->get_id();
				$group_id = null;

				if ($channel->is_uhd())
					if ($plugin_cookies->qfilter <> QualityDefs::highest)
						continue;

				if ($channel->is_hd())
					if ($plugin_cookies->qfilter == QualityDefs::lowest)
						continue;

				foreach($channel->get_genres() as $genre_id)
				{
					if ($genre_id === $uhd_genre_id)
					{
						if (!$channel->is_uhd())
							continue;

						$group_id = self::UHDTV_CHANNELS_GROUP_ID;
					}
					else if ($genre_id == $hd_genre_id)
					{
						if (!$channel->is_hd() || $channel->is_uhd())
							continue;

						$group_id = self::HDTV_CHANNELS_GROUP_ID;
					}
					else
						$group_id = '__home_tv_group_' . $genre_id;

					$this->groups[$group_id]['channels'][] = $channel_id;
					$channel->add_group($group_id);

					if ($this->groups[$group_id]['is_adult'])
						$channel->mark_as_adult();

					if ($this->tv_mode == TvModes::channels_1_to_n)
						break;
				}

				if (!is_null($group_id))
				{
					$this->channels->put($channel);
					$this->groups[self::ALL_CHANNELS_GROUP_ID]['channels'][] = $channel_id;
				}
			}

			$this->plugin->sorter->sort_channels($plugin_cookies, &$this->channels);
			hd_print('Tv channels loaded successfully!');
		}
		else
			hd_print('Tv channels loading aborted, playlist is empty!');

		hd_print('Loaded at ' . round(microtime(1) - $t, 4) . ' secs');
	}

	////////////////////////////////////////////////////////////////////////////

	public function get_day_epg($channel_id, $day_start_ts, &$plugin_cookies)
	{
		$channel = $this->get_channel($channel_id);
		$def_icon_url = is_null($poster = $channel->get_poster())? $channel->get_icon_url() : $poster;

		return
			$this->plugin->epg->get_day_epg($channel_id, $day_start_ts, $plugin_cookies, $def_icon_url);
	}

	////////////////////////////////////////////////////////////////////////////

	public function get_action_map($custom_controls_disable = false)
	{
		$actions[GUI_EVENT_TIMER] = UserInputHandlerRegistry::create_action($this, GUI_EVENT_TIMER);

		if (DUNE_FIRMWARE_BN >= 8)
		{
			if (DUNE_FIRMWARE_BN >= 10)
				$actions[GUI_EVENT_KEY_MODE] = UserInputHandlerRegistry::create_action($this, GUI_EVENT_KEY_MODE);

			if ((DUNE_FIRMWARE_BN < 11) && (DUNE_FIRMWARE_FT <> 'b4'))
				$actions[GUI_EVENT_KEY_STOP] = UserInputHandlerRegistry::create_action($this, GUI_EVENT_KEY_STOP);

			if ((DUNE_FIRMWARE_BN > 8) && !$custom_controls_disable)
			{
				if (($this->plugin_cookies_epg->enabled == ControlSwitchDefs::switch_on) && !empty($this->plugin_cookies_customs_control->epg_key))
					$actions[$this->plugin_cookies_customs_control->epg_key] = UserInputHandlerRegistry::create_action($this, 'show_custom_epg');

				if ((DUNE_FIRMWARE_BN >= 10) && !empty($this->plugin_cookies_customs_control->clock_key))
					$actions[$this->plugin_cookies_customs_control->clock_key] = UserInputHandlerRegistry::create_action($this, 'do_show_custom_clock_banner');

				if ((DUNE_FIRMWARE_BN >= 9) && !empty($this->plugin_cookies_customs_control->repeat_key))
					$actions[$this->plugin_cookies_customs_control->repeat_key] = UserInputHandlerRegistry::create_action($this, 'repeat_key');

				if (isset($actions[GUI_EVENT_KEY_INFO]))
					if ((DUNE_FIRMWARE_BN == 9) || (($this->plugin_cookies_customs_control->clock_key <> GUI_EVENT_KEY_SELECT) && ($this->plugin_cookies_customs_control->repeat_key <> GUI_EVENT_KEY_SELECT)))
						$actions[GUI_EVENT_KEY_SELECT] = UserInputHandlerRegistry::create_action($this, 'dummy');
			}

			if (NEWGUI_FEAUTURES_AVAILABLE)
				$actions[GUI_EVENT_PLAYBACK_STOP] = UserInputHandlerRegistry::create_action($this, GUI_EVENT_PLAYBACK_STOP);
		}

		return $actions;
	}

	public function get_tv_info(MediaURL $media_url, &$plugin_cookies)
	{
		$this->load_dune_zoom_deint_values();
		$this->playback_runtime = PHP_INT_MAX;
		$this->ensure_channels_loaded($plugin_cookies);
		$location_info = Wink()->get_location_info();
		$yid = $location_info->yid;

		if (!isset($media_url->screen_id))
			$this->plugin->epg->tv_info_set_faulty(0);

		if (!is_null($tv_info = $this->plugin->epg->get_tv_info()))
		{
			if (isset($media_url->group_id) && isset($media_url->channel_id))
			{
				$tv_info[PluginTvInfo::initial_group_id] = strval($media_url->group_id);
				$tv_info[PluginTvInfo::initial_channel_id] = strval($media_url->channel_id);
			}

			$tv_info[PluginTvInfo::favorite_channel_ids] = ($this->favorites_enabled)? $this->get_fav_channel_ids($plugin_cookies) : null;

			return $tv_info;
		}

		$t = microtime(1);
		$adult_channels = array();

		foreach($this->get_groups() as $group)
			if ($group['is_adult'])
				$adult_channels = array_merge($adult_channels, is_array($group['channels'])? $group['channels'] : array());

		$groups = array();
		$channels = array();
		$tv_exclude_channel_ids = $this->get_exclude_channel_ids();

		foreach ($this->groups as $group_id => $group)
		{
			if ($group_id == self::ALL_CHANNELS_GROUP_ID)
			{
				if (count($group['channels']) == 0)
					continue;

				foreach($this->channels as $c)
				{
					$channel_id = $c->get_id();

					if (!in_array($channel_id, $group['channels']) || in_array($channel_id, $tv_exclude_channel_ids))
						continue;

					if (is_null($live_url = $this->get_tv_playback_url($channel_id, 0, RET_LIVE_TEMPLATE_URL)) ||
						(!$this->plugin->pf_enabled && preg_match('/\/mdrm\/CH_.+\/manifest\.mpd/', $live_url)) ||
						(($this->parental->enabled == ControlSwitchDefs::switch_on) && $c->is_adult() && !$this->parental_passed))
					{
						$tv_exclude_channel_ids[] = $channel_id;
						continue;
					}

					$channel_groups_ids = array();

					//if ($this->tv_mode == TvModes::channels_n_to_m)
					//	$channel_groups_ids[] = self::ALL_CHANNELS_GROUP_ID;

					$tv_archive_stream_enable = 1;//!is_null($archive_url = $this->get_tv_playback_url($channel_id, 0, RET_ARCHIVE_TEMPLATE_URL)) && ($this->plugin->pf_enabled || !preg_match('/\/mdrm\/CH_.+\/manifest\.mpd/', $archive_url));
					$this->plugin->user_data->select($channel_id);

					if (is_null($ext_epg_id = $this->plugin->user_data->epg_id))
					{
						$channel_epg_ids= $c->get_epg_ids();

						foreach($this->get_epg_providers($plugin_cookies) as $epg_provider)
							if (!empty($channel_epg_ids[$epg_provider]))
							{
								$ext_epg_id = "$epg_provider@{$channel_epg_ids[$epg_provider]}";
								break;
							}
					}

					if (preg_match('/^yandex@/', $ext_epg_id))
						if (!preg_match('/yandex@.+@\d+/', $ext_epg_id))
							$ext_epg_id = rtrim($ext_epg_id, '@') . "@$yid";

					$channel =
						array
						(
							ExtTvChannel::id => $channel_id,
							ExtTvChannel::caption => $c->get_title(),
							ExtTvChannel::group_ids => array_merge($channel_groups_ids, $c->get_groups()),
							ExtTvChannel::icon_url => $c->get_icon_url(),
							ExtTvChannel::number => $c->get_number(),
							ExtTvChannel::have_archive => ($this->plugin_cookies_tv_archives->enabled == ControlSwitchDefs::switch_on) && $tv_archive_stream_enable && $c->has_archive(),
							ExtTvChannel::archive_past_sec => self::archive_past_sec,
							ExtTvChannel::archive_delay_sec => self::archive_delay_sec,
							ExtTvChannel::timeshift_hours => $c->get_timeshift_hours(),
							ExtTvChannel::playback_url_is_stream_url => false,
							ExtTvChannel::ext_epg_id => empty($ext_epg_id)? 'empty' : $ext_epg_id,
							ExtTvChannel::ext_epg_icon_url => is_null($poster = $c->get_poster())? DuneSystem::$properties['tmp_dir_path'] . '/ex_controls/dots/0x5effffff.aai' : $poster,
							ExtTvChannel::is_adult => in_array($channel_id, $adult_channels),
						);

					$channels[] = $channel;
				}

				if ($this->tv_mode == TvModes::channels_1_to_n)
					continue;

				break;
			}
		}

		foreach ($this->groups as $group_id => $group)
		{
			if ($group_id == self::ALL_CHANNELS_GROUP_ID)
				continue;

			if (($group_id == self::FAV_CHANNELS_GROUP_ID) || (($this->parental->enabled == ControlSwitchDefs::switch_on) && $group['is_adult'] && !$this->parental_passed))
				continue;

			if (count($group['channels']) == 0)
				continue;

			if (count(array_diff($group['channels'], $tv_exclude_channel_ids)) > 0)
				$groups[] =
					array
					(
						PluginTvGroup::id => $group_id,
						PluginTvGroup::caption => $group['title'],
						PluginTvGroup::icon_url => $group['icon_url'],
					);
		}

		if (empty($channels))
		{
			if (!isset($media_url->screen_id))
			{
				ControlFactory::add_multiline_label($defs, null,
					"Список каналов пуст. Прежде чем запускать воспроизведение, необходимо запустить процедуру поиска каналов.\n" .
					"Пожалуйста, в настройках плагина выберите свой регион и другие опции, затем в списке каналов нажмите \"Поиск каналов\".", 10);
				ControlFactory::add_vgap($defs, 40);
				ControlFactory::add_close_dialog_and_apply_button($button_defs, $this, null, 'open_setup_screen_btn', 'Настройки', 350);
				ControlFactoryExt::add_button_centered($defs, $button_defs, 1300);
				ControlFactory::add_close_dialog_and_apply_button($button_defs, $this, null, 'empty_playlist_dialog_close_btn', 'Отмена', 350);
				ControlFactoryExt::add_button_centered($defs, $button_defs, 1300);
				ControlFactory::add_vgap($defs, 10);

				throw new DuneException(
					get_class($this).': Starting playback is not possible: the channel list is empty! Showing dialogue...', 0,
					ActionFactorySafe::show_dialog(
						'ВНИМАНИЕ!',
						$defs,
						false,
						1300,
						array(
							'dialog_params' => array('frame_style' => (DUNE_FIRMWARE_BN <= 8)? DIALOG_FRAME_STYLE_DEFAULT : DIALOG_FRAME_STYLE_GLASS))
					)
				);
			}

			return null;
		}

		$initial_group_id = strval($media_url->group_id);
		$initial_is_favorite = 0;

		if (($this->tv_mode == TvModes::channels_1_to_n) && ($initial_group_id === self::ALL_CHANNELS_GROUP_ID))
			$initial_group_id = null;

		$this->plugin_cookies_epg = json_decode($plugin_cookies->epg);
		$actions = $this->get_action_map();

		if (DUNE_FIRMWARE_BN >= 10)
		{
			$do_cache_osd_images_action = UserInputHandlerRegistry::create_action($this, 'do_cache_osd_images');
			$actions[GUI_EVENT_TIMER] = $do_cache_osd_images_action;
			$actions[GUI_EVENT_KEY_SELECT] = $do_cache_osd_images_action;
			$actions[GUI_EVENT_KEY_INFO] = $do_cache_osd_images_action;
			$actions[GUI_EVENT_KEY_TOP_MENU] = $do_cache_osd_images_action;
		}

		$tv_info =
			array
			(
				PluginTvInfo::show_group_channels_only => $this->tv_mode,
				PluginTvInfo::groups => $groups,
				PluginTvInfo::channels => $channels,
				PluginTvInfo::favorites_supported => $this->favorites_enabled,
				PluginTvInfo::favorites_icon_url => $this->get_fav_group_icon_url(),
				PluginTvInfo::favorite_channel_ids => ($this->favorites_enabled)? $this->get_fav_channel_ids($plugin_cookies) : null,
				PluginTvInfo::initial_channel_id => strval($media_url->channel_id),
				PluginTvInfo::initial_group_id => $initial_group_id,
				PluginTvInfo::initial_is_favorite => ($initial_group_id == self::FAV_CHANNELS_GROUP_ID),
				PluginTvInfo::initial_archive_tm => isset($media_url->archive_tm)? intval($media_url->archive_tm) : -1,
				PluginTvInfo::epg_font_size => $this->plugin_cookies_epg->font_size,
				PluginTvInfo::epg_day_shift_sec => $this->plugin_cookies_location->tz_offset,
			);

		if (DUNE_FIRMWARE_BN > 8) // b9+
		{
			$tv_info[PluginTvInfo::timer] = ActionFactorySafe::timer(1000);
			$tv_info[PluginTvInfo::actions] = $actions;
		}

		PlaybackPoints::init();

		$this->plugin->epg->init(
			$tv_info,
			HomeTv::FAV_CHANNELS_GROUP_ID,
			HomeTv::ALL_CHANNELS_GROUP_ID,
			$this->plugin_cookies_epg->enabled == ControlSwitchDefs::switch_on,
			EXT_EPG_AVAILABLE && ($this->plugin_cookies_epg->ext_enable == ControlSwitchDefs::switch_on),
			$this->plugin_cookies_epg->fill_enable == ControlSwitchDefs::switch_on);

		$this->plugin->custom_epg->init(
			$this->plugin_cookies_customs_control->epg_key,
			UserInputHandlerRegistry::create_action($this, 'do_change_media_url'));

		hd_print('Tv info loaded at ' . round(microtime(1) - $t, 4) . ' secs');

		return $tv_info;
	}

	////////////////////////////////////////////////////////////////////////////

	public function handle_user_input(&$user_input, &$plugin_cookies)
	{
		static	$add_params;
		static	$caching_osd_images;
		static	$parsing_channels_opts;
		static	$custom_clock_pos;
		static	$custom_clock_osd_timer = 0;
		static	$custom_controls_osd_timer = 0;
		static	$custom_controls_disable = false;

		if (isset($user_input->control_id))
		{
			$time = time();
			PlaybackPoints::update(isset($user_input->plugin_tv_group_id)? $user_input->plugin_tv_group_id : null);

			switch ($user_input->control_id)
			{
				case GUI_EVENT_PLAYBACK_STOP:
					if (isset($user_input->playback_stop_pressed) || isset($user_input->playback_power_off_needed))
					{
						if (NEWGUI_FEAUTURES_AVAILABLE)
						{
							EpfsHandler::refresh_tv_epfs($plugin_cookies);
							$this->plugin->epg->update_epg_cache();

							return
								EpfsHandler::invalidate_folders();
						}

						return null;
					}

				case GUI_EVENT_PLAYBACK_SWITCHED:
					if (($time - $this->playback_runtime) < 3)
					{
						$actions = $this->get_action_map($custom_controls_disable);
						$do_cache_osd_images_action = UserInputHandlerRegistry::create_action($this, 'do_cache_osd_images');
						$actions[GUI_EVENT_TIMER] = $do_cache_osd_images_action;
						$actions[GUI_EVENT_KEY_SELECT] = $do_cache_osd_images_action;
						$actions[GUI_EVENT_KEY_INFO] = $do_cache_osd_images_action;
						$actions[GUI_EVENT_KEY_TOP_MENU] = $do_cache_osd_images_action;

						return
							ActionFactorySafe::change_behaviour($actions, ActionFactorySafe::timer(100));
					}

					return null;

				case 'disable_check_palette':
					$this->plugin_cookies_customs_control = json_decode($plugin_cookies->customs_control);
					$this->plugin_cookies_customs_control->check_skin_palette = ControlSwitchDefs::switch_off;
					$plugin_cookies->customs_control = json_encode($this->plugin_cookies_customs_control);

					return
						isset($user_input->post_action)? unserialize($user_input->post_action) : null;

				case 'restart_plugin':
					return
						UserInputHandlerRegistry::create_action($this->plugin->launcher, 'launch', array('mandratory_playback' => $user_input->mandratory_playback));

				case 'start_plugin':
					$this->unload_channels();

				case 'launch_plugin':
					$custom_clock_osd_timer = 0;
					$custom_controls_disable = false;
					$this->plugin_cookies_customs_control = json_decode($plugin_cookies->customs_control);

					if (!isset($user_input->dialog_frame_style))
						$user_input->dialog_frame_style = DIALOG_FRAME_STYLE_DEFAULT;

					if (!isset($user_input->mandratory_playback))
						$user_input->mandratory_playback = ($plugin_cookies->autoplay_enable == ControlSwitchDefs::switch_on);

					if ($plugin_cookies->app_version <> $this->plugin->app_version)
						$plugin_cookies->app_version = $this->plugin->app_version;

					if ($this->plugin_cookies_customs_control->check_skin_palette == ControlSwitchDefs::switch_on)
					{
						$params =
							array
							(
								'dialog_frame_style' => $user_input->dialog_frame_style,
								'mandratory_playback' => $user_input->mandratory_playback,
							);

						if (isset($user_input->initial_media_url))
							$params['initial_media_url'] = $user_input->initial_media_url;

						if ($skin_palette_check_action = DunePalettePatcher::get_skin_palette_check_action(
								$tv_run_action = ActionFactorySafe::update_dvb_channels(UserInputHandlerRegistry::create_action($this, $user_input->control_id, $params)),
								UserInputHandlerRegistry::create_action($this, 'disable_check_palette', array('post_action' => serialize($tv_run_action))),
								$user_input->dialog_frame_style))
							return $skin_palette_check_action;
					}

					$this->plugin_cookies_location = json_decode($plugin_cookies->location);
					$playlist = Cache::get_latest_file(get_paved_path(PLAYLISTS_CACHE_PATH . "/tv/{$this->plugin_cookies_location->id}"));

					if (!empty($playlist))
					{
						$playlist = intval($playlist);

						if (!empty($plugin_cookies->channels_search_delay) &&
							(time() > $playlist) && // Грубая проверка системного времени на корректность
							(time() > (intval($plugin_cookies->channels_search_delay) + $playlist)) &&
							(time() > $plugin_cookies->scan_not_before))
						{
							$add_params = array();

							if (isset($user_input->mandratory_playback))
								$add_params['mandratory_playback'] = $user_input->mandratory_playback;

							if (isset($user_input->dialog_frame_style))
								$add_params['dialog_frame_style'] = $user_input->dialog_frame_style;

							if (isset($user_input->initial_media_url))
								$add_params['initial_media_url'] = $user_input->initial_media_url;

							ControlFactory::add_vgap($defs, 20);
							ControlFactory::add_multiline_label($defs, null, 'Плейлист из хранилища устарел (файл от ' . strftime('%d.%m.%Y', $playlist) . '), пришло время его обновить. Продолжить?', 3);
							ControlFactory::add_vgap($defs, 40);
							ControlFactory::add_close_dialog_and_apply_button($button_defs, $this, array_merge($add_params, array('post_action_id' => 'tv_play')), 'do_start_scan', 'Обновить', 350);
							ControlFactoryExt::add_button_centered($defs, $button_defs, 1100);
							ControlFactory::add_close_dialog_and_apply_button($button_defs, $this, $add_params, 'scan_cancel', 'Отмена', 350);
							ControlFactoryExt::add_button_centered($defs, $button_defs, 1100);
							ControlFactory::add_vgap($defs, 20);

							return
								ActionFactorySafe::show_dialog(
									$this->plugin->app_caption,
									$defs,
									false,
									1100,
									array
									(
										'dialog_params' => array('frame_style' => ((isset($user_input->dialog_frame_style))? $user_input->dialog_frame_style : DIALOG_FRAME_STYLE_DEFAULT)),
									));
						}
					}
					else
						return
							SplashScreen::close_and_run(ActionFactorySafe::open_folder(TvGroupsScreen::get_media_url_str(TvBrowseModeDefs::normal)));//FoldersManager::open_folder(TvGroupsScreen::get_media_url_str(TvBrowseModeDefs::normal)));

				case 'tv_play':
					PlaybackPoints::init();

				case 'start_playback':
					if (!isset($this->plugin_cookies_customs_control))
						$this->plugin_cookies_customs_control = json_decode($plugin_cookies->customs_control);

					if (isset($user_input->initial_media_url))
					{
						$tv_play_action = ActionFactorySafe::tv_play(MediaURL::decode($user_input->initial_media_url));
						//r12
						if ((get_platform_kind() == 'android') && (DUNE_FIRMWARE_BN < 13))
							return $tv_play_action;
						// b8+, r10-11, r13
						return
							SplashScreen::open_and_run($tv_play_action);
					}

					if (($plugin_cookies->autoplay_enable == ControlSwitchDefs::switch_off) &&
						(!isset($user_input->mandratory_playback) || !$user_input->mandratory_playback))
							return
								SplashScreen::close_and_run(ActionFactorySafe::open_folder(TvGroupsScreen::get_media_url_str(TvBrowseModeDefs::normal)));

				case 'pin_field':
					if ($user_input->control_id == 'pin_field')
					{
						$this->parental_passed = (md5(DuneSystem::$properties['plugin_name'] . $user_input->pin_field) === $this->parental->pin);

						if (isset($user_input->initial_media_url))
							return
								ActionFactorySafe::close_dialog_and_run(UserInputHandlerRegistry::create_action($this, 'tv_play'));
					}

					$media_url = null;

					if (isset($user_input->selected_media_url))
					{
						$selected_media_url = MediaURL::decode($user_input->selected_media_url);

						if (isset($selected_media_url->group_id))
						{
							if ((DUNE_FIRMWARE_BN <= 9) || ((get_platform_kind() == 'android') && (DUNE_FIRMWARE_BN < 13)))
								return
									SplashScreen::close_and_run(ActionFactorySafe::tv_play($selected_media_url));

							$media_url = MediaURL::decode();
							$media_url->group_id = isset($selected_media_url->group_id)? $selected_media_url->group_id : HomeTv::ALL_CHANNELS_GROUP_ID;
							$media_url->channel_id = $selected_media_url->channel_id;
							$media_url->archive_tm = -1;
							$media_url->is_favorite = $media_url->group_id == HomeTv::FAV_CHANNELS_GROUP_ID;

							if ($user_input->control_id == 'pin_field')
								return
									ActionFactorySafe::close_dialog_and_run(UserInputHandlerRegistry::create_action($this, 'tv_play', array('initial_media_url' => $media_url->get_raw_string())));

							return
								ActionFactorySafe::tv_play($media_url);
						}
					}

					if (file_exists('/config/resume_state.properties'))
					{
						$resume_state = parse_ini_file('/config/resume_state.properties', 0, INI_SCANNER_RAW);

						if (strpos($resume_state['plugin_tv_group'], '_home_tv_') !== false)
						{
							$media_url = MediaURL::decode();
							$media_url->group_id = $resume_state['plugin_tv_is_favorite']? HomeTv::FAV_CHANNELS_GROUP_ID : $resume_state['plugin_tv_group'];
							$media_url->channel_id = $resume_state['plugin_tv_channel'];
							$media_url->archive_tm = ((time() - $resume_state['plugin_tv_archive_tm']) < HomeTv::archive_past_sec)? $resume_state['plugin_tv_archive_tm'] : -1;
							$media_url->is_favorite = $resume_state['plugin_tv_is_favorite'];
						}
					}

					if ($user_input->control_id == 'pin_field')
						return
							ActionFactorySafe::close_dialog_and_run(UserInputHandlerRegistry::create_action($this, 'tv_play', array('initial_media_url' => $media_url->get_raw_string())));

					return
						ActionFactorySafe::tv_play($media_url);

				case GUI_EVENT_KEY_STOP:
					return
						ActionFactorySafe::close_and_run(SplashScreen::close_and_run());

				case 'repeat_key':
					if (!is_null($media_url = PlaybackPoints::get_prev()))
					{
						$playback_group_id = isset($user_input->plugin_tv_group_id)? $user_input->plugin_tv_group_id : (in_array($media_url->channel_id, $this->get_fav_channel_ids($plugin_cookies))? HomeTv::FAV_CHANNELS_GROUP_ID : HomeTv::ALL_CHANNELS_GROUP_ID);
						$media_url->group_id = $playback_group_id;
						$media_url->is_favorite = ($playback_group_id == HomeTv::FAV_CHANNELS_GROUP_ID);
						$media_url->archive_tm += $media_url->position;
						$user_input->initial_media_url = $media_url->get_raw_string();
						$this->plugin->epg->tv_info_set_faulty(0);
					}
					else
						return null;

				case 'do_change_media_url':
					$action = ActionFactorySafe::close_and_run(UserInputHandlerRegistry::create_action($this, 'start_playback', array('initial_media_url' => $user_input->initial_media_url)));
					$action_map = $this->get_action_map($custom_controls_disable);

					if (isset($action_map[GUI_EVENT_PLAYBACK_STOP]))
					{
						unset($action_map[GUI_EVENT_PLAYBACK_STOP]);
						return ActionFactory::change_behaviour($action_map, null, $action);

					}

					return $action;

				case 'do_cache_osd_images':
					$caching_osd_images = true;
					$custom_clock_pos = explode(',', $this->plugin_cookies_customs_control->clock_pos);
					$actions = $this->get_action_map($custom_controls_disable);

					if (($time - $this->playback_runtime) < 3)
					{
						if ((get_playback_state() == PLAYBACK_PLAYING) && ($this->playback_runtime == PHP_INT_MAX))
							$this->playback_runtime = $time;

						$do_cache_osd_images_action = UserInputHandlerRegistry::create_action($this, 'do_cache_osd_images', null);
						$actions[GUI_EVENT_TIMER] = $do_cache_osd_images_action;
						$actions[GUI_EVENT_KEY_SELECT] = $do_cache_osd_images_action;
						$actions[GUI_EVENT_KEY_INFO] = $do_cache_osd_images_action;
						$actions[GUI_EVENT_KEY_TOP_MENU] = $do_cache_osd_images_action;
					}

					return
						ActionFactorySafe::change_behaviour($actions, ActionFactorySafe::timer(500), $this->get_ext_epg_init_action());

				case 'open_setup_screen_btn':
					return
						ActionFactorySafe::open_folder(SettingsScreen::get_media_url_str());

				case 'empty_playlist_dialog_close_btn':
					return
						SplashScreen::close_and_run();

				case 'input_pin_dialog_press_key_return':
					return
						ActionFactorySafe::close_dialog();

				case 'groups_screen_update':
					$this->unload_channels();
					$media_urls =
						array
						(
							TvGroupsScreen::get_media_url_str(),
							TvChannelsScreen::get_active_media_url_str(),
							TvFavoritesScreen::get_active_media_url_str(),
						);

					$post_action = ActionFactorySafe::update_regular_folder(TvGroupsScreen::get_regular_folder_range(MediaURL::decode($user_input->parent_media_url), 0, $plugin_cookies), true, $user_input->sel_ndx);

					if (NEWGUI_FEAUTURES_AVAILABLE)
						return
							EpfsHandler::invalidate_folders($media_urls, $post_action);

					return
						ActionFactorySafe::invalidate_folders($media_urls, $post_action);

				case 'do_show_scan_opts':
					$parsing_channels_opts = json_decode(isset($plugin_cookies->parsing_channels_opts)? $plugin_cookies->parsing_channels_opts : '{"enable_grouping":"' . ControlSwitchDefs::switch_on . '"}');
					$add_params =
						array
						(
							'grouping_enabled' => $parsing_channels_opts->enable_grouping,
							'parsing_channels_opts_changed' => !isset($plugin_cookies->parsing_channels_opts),
							'post_action_id' => isset($user_input->post_action_id)? $user_input->post_action_id : 'start_plugin',
							'post_action_params' => isset($user_input->post_action_params)? $user_input->post_action_params : '',
							'mandratory_playback' => isset($user_input->mandratory_playback)? $user_input->mandratory_playback : intval($plugin_cookies->autoplay_enable == ControlSwitchDefs::switch_on),
							'dialog_frame_style' => isset($user_input->dialog_frame_style)? $user_input->dialog_frame_style : DIALOG_FRAME_STYLE_DEFAULT,
						);

					return ActionFactorySafe::show_dialog(
						'Опции парсера',
						$this->get_scan_opts_dialog_controls_defs($add_params),
						isset($user_input->close_by_return)? (boolean)$user_input->close_by_return : true,
						1200,
						array
						(
							'initial_sel_ndx' => 1,
							'dialog_params' =>
								array
								(
									'frame_style' => $add_params['dialog_frame_style'],
								),
						));

				case 'scan_opts_combo':
					$add_params['grouping_enabled'] = $user_input->{$user_input->control_id};
					$add_params['parsing_channels_opts_changed'] = ($add_params['grouping_enabled'] <> $parsing_channels_opts->enable_grouping);

					return
						ActionFactorySafe::reset_controls($this->get_scan_opts_dialog_controls_defs($add_params));

				case 'save_scan_opts':
					if ($add_params['parsing_channels_opts_changed'])
						$plugin_cookies->parsing_channels_opts = json_encode(array('enable_grouping' => $add_params['grouping_enabled']));

					return
						ActionFactorySafe::close_dialog_and_run(UserInputHandlerRegistry::create_action($this, 'do_start_scan', $add_params));

				case 'do_start_scan':
					$this->plugin_cookies_location = json_decode($plugin_cookies->location);
					$this->plugin->user_data->load(USER_DATA_CACHE_PATH . "/{$this->plugin_cookies_location->id}");
					Wink()->set_location($this->plugin_cookies_location->id);
					$this->plugin_cookies_playlist_export = json_decode($plugin_cookies->playlist_export);
					$this->plugin_cookies_playlist_export->path_m3u = ($this->plugin_cookies_playlist_export->enabled == ControlSwitchDefs::switch_on)? $this->plugin_cookies_playlist_export->path_m3u : '';
					$parsing_channels_opts = json_decode($plugin_cookies->parsing_channels_opts);
					$add_params =
						array
						(
							'phase' => 0,
							'grouping_enabled' => ($parsing_channels_opts->enable_grouping == ControlSwitchDefs::switch_on),
							'supposed_lines_count' => (empty($this->plugin_cookies_playlist_export->path_m3u)? 7 : 8) - (($parsing_channels_opts->enable_grouping == ControlSwitchDefs::switch_off)? 1 : 0),
							'post_action_id' => $user_input->post_action_id,
							'post_action_params' => isset($user_input->post_action_params)? $user_input->post_action_params : '',
							'dialog_frame_style' => $user_input->dialog_frame_style,
							'mandratory_playback' => $user_input->mandratory_playback,
							'search_log_head' => 'Поиск плейлистов...',
							'search_log_tail' => "\n",
							'channels_compare_log' => '',
						);

					if (isset($user_input->initial_media_url))
						$add_params['initial_media_url'] = $user_input->initial_media_url;

					return
						ActionFactorySafe::show_dialog(
							'Поиск каналов',
							$this->get_scan_dialog_controls_defs($add_params),
							false,
							950,
							array
							(
								'dialog_params' => array('frame_style' => ((isset($user_input->dialog_frame_style))? $user_input->dialog_frame_style : DIALOG_FRAME_STYLE_DEFAULT)),
								'actions' =>
								array
								(
									GUI_EVENT_TIMER => $this->do_update_scan_dialog_action($add_params, $plugin_cookies),
									GUI_EVENT_KEY_ENTER => ActionFactorySafe::close_dialog_and_run(UserInputHandlerRegistry::create_action($this, $add_params['post_action_id'], $add_params)),
								),
								'timer' => ActionFactorySafe::timer(100),
							));

				case 'do_progress_scan':
					$params =
						array
						(
							'dialog_frame_style' => $add_params['dialog_frame_style'],
							'mandratory_playback' => $add_params['mandratory_playback'],
						);

					if (isset($add_params['initial_media_url']))
						$params['initial_media_url'] = $add_params['initial_media_url'];

					if (isset($add_params['post_action_params']))
					{
						$params['post_action_params'] = $add_params['post_action_params'];

						if (!empty($add_params['post_action_params']))
							$params += unserialize($add_params['post_action_params']);
					}

					$actions[GUI_EVENT_KEY_ENTER] = ActionFactorySafe::close_dialog_and_run(UserInputHandlerRegistry::create_action($this, $add_params['post_action_id'], $params));

					if ($add_params['phase'] < 11)
						$actions[GUI_EVENT_TIMER] = $this->do_update_scan_dialog_action($add_params, $plugin_cookies);

					return
						ActionFactorySafe::reset_controls(
							$this->get_scan_dialog_controls_defs($add_params),
							ActionFactorySafe::change_behaviour($actions, ActionFactorySafe::timer(100)));

				case 'scan_cancel':
					$plugin_cookies->scan_not_before = time() + 86400;

					return
						UserInputHandlerRegistry::create_action($this, 'tv_play', $add_params);

				case 'do_show_custom_clock_banner':
					$custom_clock_osd_timer = ($custom_clock_osd_timer < $time)? PHP_INT_MAX : 0;
					break;

				case GUI_EVENT_KEY_MODE:
					if ($custom_controls_osd_timer > $time)
						$custom_controls_disable = !$custom_controls_disable;

					$custom_controls_osd_timer = $time + 3;
					break;

				case 'show_custom_epg':
					$playback_channel_id = $user_input->plugin_tv_channel_id;
					$playback_group_id = isset($user_input->plugin_tv_group_id)? $user_input->plugin_tv_group_id : (!in_array($playback_channel_id, $this->get_fav_channel_ids($plugin_cookies))? HomeTv::ALL_CHANNELS_GROUP_ID : HomeTv::FAV_CHANNELS_GROUP_ID);

					return
						$this->plugin->custom_epg->show($playback_channel_id, $playback_group_id);
			}

			// Fix the bug of switching the scaling and deinterlacing
			if ($this->fix_dune_zoom_deint_enabled)
				$this->fix_dune_zoom_deint_values();
			else
				$this->fix_dune_zoom_deint_enabled = true;
			///////////////////////////////////////////////////////////

			$playback_channel_id = $user_input->plugin_tv_channel_id;
			$playback_group_id = isset($user_input->plugin_tv_group_id)? $user_input->plugin_tv_group_id : (((get_platform_kind() == 'android') && !in_array($playback_channel_id, $this->get_fav_channel_ids($plugin_cookies)))? HomeTv::ALL_CHANNELS_GROUP_ID : HomeTv::FAV_CHANNELS_GROUP_ID);

			if (!is_null($reminder_dialog_action = $this->plugin->custom_epg->get_reminder_dialog_action($playback_channel_id, $playback_group_id, $plugin_cookies)))
				return
					ActionFactorySafe::change_behaviour($this->get_action_map($custom_controls_disable), ActionFactorySafe::timer(1), $reminder_dialog_action);

			if (DUNE_FIRMWARE_BN >= 10)
			{
				if ($caching_osd_images)
				{
					$caching_osd_images = false;
					$action = ActionFactorySafe::change_behaviour($this->get_action_map($custom_controls_disable), ActionFactorySafe::timer(1500));

					return
						preg_match('/87../', get_platform_kind())? // Фикс зависания на скине "Holography" для соло и дуо4к
							$action :
							OSDComponentFactory::get_caching_osd_images_action(
								$action,
								PLUGIN_IMG_PATH . '/clock.png',
								PLUGIN_IMG_PATH . '/alert.png');
				}

				if ($custom_controls_osd_timer > $time)
				{
					$dx = ($custom_clock_pos[0] > 120) || ($custom_clock_pos[1] > 80)? 120 : 860;
					OSDComponentFactory::add_content_box($comps, $dx, 80, $custom_controls_disable? 912 : 896, 78);
					ActionFactorySafe::add_osd_image($comps, PLUGIN_IMG_PATH . '/alert.png', $dx + 18, 80 + 9);
					ActionFactorySafe::add_osd_text($comps, 'Управление надстройками ' . ($custom_controls_disable? 'отключено' : 'включено'), $dx + 18 + 60 + 18, 80 + 13);
				}

				if (!$user_input->playback_browser_activated && ($custom_clock_osd_timer > $time))
				{
					OSDComponentFactory::add_content_box($comps, $custom_clock_pos[0], $custom_clock_pos[1], 226, 78);
					ActionFactorySafe::add_osd_image($comps, PLUGIN_IMG_PATH . '/clock.png', $custom_clock_pos[0] + 20, $custom_clock_pos[1] + 18);
					ActionFactorySafe::add_osd_text($comps, strftime('%H:%M', $time - $this->plugin_cookies_location->tz_offset), $custom_clock_pos[0] + 80, $custom_clock_pos[1] + 13);
				}

				if (!isset($comps))
					$comps = array();

				return
					ActionFactorySafe::update_osd(
						$comps,
						ActionFactorySafe::change_behaviour(
							$this->get_action_map($custom_controls_disable),
							ActionFactorySafe::timer(1000)));
			}

			return 	ActionFactorySafe::change_behaviour(
				$this->get_action_map($custom_controls_disable),
				ActionFactorySafe::timer(1000));
		}
	}

	public function open_settings($media_url, $post_action = null, $dialog_frame_style = DIALOG_FRAME_STYLE_DEFAULT)
	{
		return
			$this->plugin->settings_screen->show_dialog($media_url, $dialog_frame_style, $post_action);
	}

	///////////////////////////////////////////////////////////////////////////

	public function get_fav_channel_ids(&$plugin_cookies, $ret_string = false)
	{
		$this->plugin->user_data->select(0);

		return
			is_null($fav_channel_ids = $this->plugin->user_data->favorite_ids)? ($ret_string? '' : array()) : ($ret_string? $fav_channel_ids : explode(',', $fav_channel_ids));
	}

	public function set_fav_channel_ids(&$fav_channel_ids, &$plugin_cookies)
	{
		$this->plugin->user_data->select(0);
		$favorite_ids = $this->plugin->user_data->favorite_ids;
		$this->plugin->user_data->favorite_ids = empty($fav_channel_ids)? null : trim(implode(',', $fav_channel_ids), ',');
		$this->plugin->user_data->save();
	}

	public function get_exclude_channel_ids($ret_string = false)
	{
		$this->plugin->user_data->select(0);

		return
			is_null($exclude_channel_ids = $this->plugin->user_data->exclude_ids)? ($ret_string? '' : array()) : ($ret_string? $exclude_channel_ids : explode(',', $exclude_channel_ids));
	}

	public function set_exclude_channel_ids(&$exclude_ids)
	{
		$this->plugin->user_data->select(0);
		$this->plugin->user_data->exclude_ids = empty($exclude_ids)? null : trim(implode(',', $exclude_ids), ',');
		$this->plugin->user_data->save();
	}

	///////////////////////////////////////////////////////////////////////////

	public function add_special_groups(&$items, &$plugin_cookies)
	{
		$items[] = array
		(
			PluginRegularFolderItem::media_url => TvBinScreen::get_media_url_str(),
			PluginRegularFolderItem::caption => 'Корзина',
			PluginRegularFolderItem::view_item_params =>
				array
				(
					ViewItemParams::icon_path => 'plugin_file://img/defaults/groups/bin_group.png',
					ViewItemParams::item_caption_color => DEF_LABEL_TEXT_COLOR_DARKGRAY,//DEF_LABEL_TEXT_COLOR_SILVER,
				)
		);
	}

	///////////////////////////////////////////////////////////////////////

	public function get_tv_stream_url($media_url, &$plugin_cookies)
	{
		if (preg_match('/mpegts|mpeg.ts|\.ts/i', $media_url))
			if (!preg_match('/ts:\/\//i', $media_url))
				$media_url = preg_replace('/http.*:\/\//i', "$0ts://", $media_url);

		if (preg_match('/(85\.94\.1\.\d+)(:\d+|)\/hls\/CH_/i', $media_url))
			$media_url = preg_replace('/(85\.94\.1)\.\d+(:\d+|)/i', "$1.9:8802", $media_url);

		return $media_url;
	}

	///////////////////////////////////////////////////////////////////////
	// Archive.

	public function get_archive(MediaURL $media_url, &$plugin_cookies)
	{ return null; }

	///////////////////////////////////////////////////////////////////////
	// Hooks.

	public function folder_entered(MediaURL $media_url, &$plugin_cookies)
	{ /* Nop */ }

	///////////////////////////////////////////////////////////////////////////
	// Plugin manifest

	public function get_plugin_manifest_template($parental_enable = false)
	{
		$handler_id = $this->plugin->launcher->get_handler_id();

		if ($parental_enable)
			$pin_menu_item =
<<<SECTION

		  <menu_item>
			<caption>Ввод PIN</caption>
			<icon_url>plugin_file://img/input_pin.png</icon_url>
			<action>
			  <type>plugin_handle_user_input</type>
			  <params>
				<handler_id>$handler_id</handler_id>
				<control_id>plugin_entry</control_id>
				<action_id>input_pin</action_id>
				<mandratory_playback>1</mandratory_playback>
			  </params>
			</action>
		  </menu_item>
SECTION;
		else
			$pin_menu_item = '';

		return
<<<MANIFEST
<?xml version="1.0" encoding="utf-8"?>
<dune_plugin>
  <name>{name}</name>
  <version_index>{version_index}</version_index>
  <version>{version}</version>
  <caption>{caption}</caption>
  <type>php</type>
  <global_actions>
	<boot_end>
	  <type>plugin_system</type>
	  <data>
		<run_string>bin/register_supplier</run_string>
	  </data>
	</boot_end>
	<install>
	  <type>plugin_system</type>
	  <data>
		<run_string>echo &gt;\$FS_PREFIX/tmp/home_tv_was_installed_mark</run_string>
	  </data>
	</install>
	<uninstall>
	  <type>plugin_handle_user_input</type>
	  <params>
		<handler_id>$handler_id</handler_id>
		<control_id>plugin_entry</control_id>
		<action_id>uninstall</action_id>
	  </params>
	</uninstall>
	<update>
	  <type>plugin_system</type>
	  <data>
		<run_string>echo &gt;\$FS_PREFIX/tmp/home_tv_was_updated_mark</run_string>
	  </data>
	</update>
  </global_actions>
  <embeddable_plugin_folders>
	<enabled>yes</enabled>
	<update_action>
	  <type>plugin_handle_user_input</type>
	  <data>
		<show_dialog_delay>10000</show_dialog_delay>
	  </data>
	  <params>
		<handler_id>$handler_id</handler_id>
		<control_id>plugin_entry</control_id>
		<action_id>update_epfs</action_id>
	  </params>
	</update_action>
	<mapping>
	  <tv>htv</tv>
	</mapping>
  </embeddable_plugin_folders>
  <entry_points>
	<entry_point>
	  <parent_media_url>root://tv</parent_media_url>
	  <media_url>run</media_url>
	  <caption>{label}</caption>
	  <icon_url>plugin_file://img/plugin_icon.png</icon_url>
	  <badge_icon_url>plugin_file://img/transparent.aai</badge_icon_url>
	  <small_icon_url>plugin_file://img/plugin_icon_small.png</small_icon_url>
	  <override_default_badge>yes</override_default_badge>
	  <icon_fit_scale_factor>1.05</icon_fit_scale_factor>
	  <ip_address_required>yes</ip_address_required>
	  <valid_time_required>no</valid_time_required>
	  <actions>
		<key_enter>
		  <type>plugin_handle_user_input</type>
		  <params>
			<handler_id>$handler_id</handler_id>
			<control_id>plugin_entry</control_id>
			<action_id>launch</action_id>
			<mandratory_playback>0</mandratory_playback>
		  </params>
		</key_enter>
		<key_play>
		  <type>plugin_handle_user_input</type>
		  <params>
			<handler_id>$handler_id</handler_id>
			<control_id>plugin_entry</control_id>
			<action_id>launch</action_id>
			<mandratory_playback>1</mandratory_playback>
		  </params>
		</key_play>
	  </actions>
	  <popup_menu>
		<menu_items>$pin_menu_item
		  <menu_item>
			<caption>Настройки плагина</caption>
			<icon_url>plugin_file://img/settings.png</icon_url>
			<action>
			  <type>plugin_handle_user_input</type>
			  <params>
				<handler_id>$handler_id</handler_id>
				<control_id>plugin_entry</control_id>
				<action_id>open_setup</action_id>
			  </params>
			</action>
		  </menu_item>
		  <menu_item>
			<caption>Перезагрузить DUNE</caption>
			<icon_url>plugin_file://img/hard_reset.png</icon_url>
			<action>
			  <type>plugin_handle_user_input</type>
			  <params>
				<handler_id>$handler_id</handler_id>
				<control_id>hard_reset</control_id>
			  </params>
			</action>
		  </menu_item>
		</menu_items>
	  </popup_menu>
	</entry_point>
  </entry_points>
  <params>
	<program>home_tv_plugin.php</program>
  </params>
  <auto_resume>
	<enable>{auto_resume}</enable>
	<action>
		<type>plugin_handle_user_input</type>
		<params>
			<handler_id>$handler_id</handler_id>
			<control_id>plugin_entry</control_id>
		   <mandratory_playback>1</mandratory_playback>
		</params>
	</action>
	<ip_address_required>yes</ip_address_required>
	<valid_time_required>yes</valid_time_required>
  </auto_resume>
  <operation_timeout>
	<default>240</default>
	<get_epg_day>30</get_epg_day>
  </operation_timeout>
  <operation_progress_dialog_show_delay>
	<default>20000</default>
  </operation_progress_dialog_show_delay>
  <progress_dialog_title>Пожалуйста, подождите...</progress_dialog_title>
  <check_update>
	<schema>2</schema>
	<url>http://127.0.0.1/cgi-bin/plugins/home_tv/update</url>
	<timeout>3</timeout>
	<required>no</required>
	<auto>yes</auto>
  </check_update>
</dune_plugin>
MANIFEST;
	}

	///////////////////////////////////////////////////////////////////////////////

}

?>