<?php

###############################################################################
#
# Dune STB API
#
# This module is a collection of definitions and simplest functions for
# convenient interaction with the built-in Dune HD GUI Framework in php plugins.
# The module does not pretend to be a full-fledged API, although in terms of
# functionality and capabilities close to Dune JavaScript STB API.
#
# Using the module in your projects is not prohibited. Third party help in
# development is strongly encouraged. Send comments to brigadir@mydune.ru
#
# Author: Brigadir (forum.mydune.ru)
# Date: 23-07-2017
# Latest update: 13-05-2023
#
# PS: Certain parts of the functionality may not work on different firmware.
#     I hope this will be fixed with the release of new firmware.
#
###############################################################################

# Command execution status values
define('COMMAND_STATUS_SUCCESS',							'ok');
define('COMMAND_STATUS_FAIL',								'failed');
define('COMMAND_STATUS_TIMEOUT',							'timeout');

# Gui Event Keys
if (!defined('GUI_EVENT_KEY_ENTER'))			define('GUI_EVENT_KEY_ENTER',           'key_enter');
if (!defined('GUI_EVENT_KEY_PLAY'))				define('GUI_EVENT_KEY_PLAY',            'key_play');
if (!defined('GUI_EVENT_KEY_A_RED'))			define('GUI_EVENT_KEY_A_RED',           'key_a_red');
if (!defined('GUI_EVENT_KEY_B_GREEN'))			define('GUI_EVENT_KEY_B_GREEN',         'key_b_green');
if (!defined('GUI_EVENT_KEY_C_YELLOW'))			define('GUI_EVENT_KEY_C_YELLOW',        'key_c_yellow');
if (!defined('GUI_EVENT_KEY_D_BLUE'))			define('GUI_EVENT_KEY_D_BLUE',          'key_d_blue');
if (!defined('GUI_EVENT_KEY_POPUP_MENU'))		define('GUI_EVENT_KEY_POPUP_MENU',      'key_popup_menu');
if (!defined('GUI_EVENT_KEY_INFO'))				define('GUI_EVENT_KEY_INFO',            'key_info');
if (!defined('GUI_EVENT_KEY_LEFT'))				define('GUI_EVENT_KEY_LEFT',            'key_left');
if (!defined('GUI_EVENT_KEY_RIGHT'))			define('GUI_EVENT_KEY_RIGHT',           'key_right');
if (!defined('GUI_EVENT_KEY_UP'))				define('GUI_EVENT_KEY_UP',              'key_up');
if (!defined('GUI_EVENT_KEY_DOWN'))				define('GUI_EVENT_KEY_DOWN',            'key_down');
if (!defined('GUI_EVENT_KEY_P_PLUS'))			define('GUI_EVENT_KEY_P_PLUS',          'key_p_plus');
if (!defined('GUI_EVENT_KEY_P_MINUS'))			define('GUI_EVENT_KEY_P_MINUS',         'key_p_minus');
if (!defined('GUI_EVENT_KEY_NEXT'))				define('GUI_EVENT_KEY_NEXT',            'key_next');
if (!defined('GUI_EVENT_KEY_PREV'))				define('GUI_EVENT_KEY_PREV',            'key_prev');
if (!defined('GUI_EVENT_KEY_FIP_NEXT'))			define('GUI_EVENT_KEY_FIP_NEXT',        'key_fip_next');
if (!defined('GUI_EVENT_KEY_FIP_PREV'))			define('GUI_EVENT_KEY_FIP_PREV',        'key_fip_prev');
if (!defined('GUI_EVENT_KEY_SETUP'))			define('GUI_EVENT_KEY_SETUP',           'key_setup');
if (!defined('GUI_EVENT_KEY_RETURN'))			define('GUI_EVENT_KEY_RETURN',          'key_return');
if (!defined('GUI_EVENT_KEY_SELECT'))			define('GUI_EVENT_KEY_SELECT',          'key_select');
if (!defined('GUI_EVENT_KEY_CLEAR'))			define('GUI_EVENT_KEY_CLEAR',           'key_clear');
if (!defined('GUI_EVENT_KEY_PAUSE'))			define('GUI_EVENT_KEY_PAUSE',           'key_pause');
if (!defined('GUI_EVENT_KEY_FWD'))				define('GUI_EVENT_KEY_FWD',				'key_fwd');
if (!defined('GUI_EVENT_KEY_REW'))				define('GUI_EVENT_KEY_REW',				'key_rew');
if (!defined('GUI_EVENT_KEY_SLOW'))				define('GUI_EVENT_KEY_SLOW',			'key_slow');
if (!defined('GUI_EVENT_KEY_STOP'))				define('GUI_EVENT_KEY_STOP',			'key_stop');
if (!defined('GUI_EVENT_KEY_TOP_MENU'))			define('GUI_EVENT_KEY_TOP_MENU',		'key_top_menu');
if (!defined('GUI_EVENT_KEY_POWER'))			define('GUI_EVENT_KEY_POWER',			'key_power');
if (!defined('GUI_EVENT_KEY_EJECT'))			define('GUI_EVENT_KEY_EJECT',			'key_eject');
if (!defined('GUI_EVENT_KEY_MODE'))				define('GUI_EVENT_KEY_MODE',			'key_mode');
if (!defined('GUI_EVENT_KEY_VENDOR'))			define('GUI_EVENT_KEY_VENDOR',			'key_vendor');
if (!defined('GUI_EVENT_KEY_SHUFFLE'))			define('GUI_EVENT_KEY_SHUFFLE',			'key_shuffle');
if (!defined('GUI_EVENT_KEY_MUTE'))				define('GUI_EVENT_KEY_MUTE',			'key_mute');
if (!defined('GUI_EVENT_KEY_V_PLUS'))			define('GUI_EVENT_KEY_V_PLUS',			'key_v_plus');
if (!defined('GUI_EVENT_KEY_V_MINUS'))			define('GUI_EVENT_KEY_V_MINUS',			'key_v_minus');
if (!defined('GUI_EVENT_KEY_SEARCH'))			define('GUI_EVENT_KEY_SEARCH',			'key_search');
if (!defined('GUI_EVENT_KEY_ZOOM'))				define('GUI_EVENT_KEY_ZOOM',			'key_zoom');
if (!defined('GUI_EVENT_KEY_SUBTITLE'))			define('GUI_EVENT_KEY_SUBTITLE',		'key_subtitle');
if (!defined('GUI_EVENT_KEY_REPEAT'))			define('GUI_EVENT_KEY_REPEAT',			'key_repeat');
if (!defined('GUI_EVENT_KEY_AUDIO'))			define('GUI_EVENT_KEY_AUDIO',			'key_audio');
if (!defined('GUI_EVENT_KEY_REC'))				define('GUI_EVENT_KEY_REC',				'key_rec');
if (!defined('GUI_EVENT_KEY_DUNE'))				define('GUI_EVENT_KEY_DUNE',			'key_dune');
if (!defined('GUI_EVENT_KEY_URL'))				define('GUI_EVENT_KEY_URL',				'key_url');
if (!defined('GUI_EVENT_KEY_KEYBRD'))			define('GUI_EVENT_KEY_KEYBRD',			'key_keybrd');
if (!defined('GUI_EVENT_KEY_MOUSE'))			define('GUI_EVENT_KEY_MOUSE',			'key_mouse');
if (!defined('GUI_EVENT_KEY_RECENT'))			define('GUI_EVENT_KEY_RECENT',			'key_recent');
if (!defined('GUI_EVENT_KEY_TV'))				define('GUI_EVENT_KEY_TV',				'key_tv');
if (!defined('GUI_EVENT_KEY_MOVIES'))			define('GUI_EVENT_KEY_MOVIES',			'key_movies');
if (!defined('GUI_EVENT_KEY_MUSIC'))			define('GUI_EVENT_KEY_MUSIC',			'key_music');
if (!defined('GUI_EVENT_KEY_ANGLE'))			define('GUI_EVENT_KEY_ANGLE',			'key_angle');
if (!defined('GUI_EVENT_KEY_0'))				define('GUI_EVENT_KEY_0',               'key_0');
if (!defined('GUI_EVENT_KEY_1'))				define('GUI_EVENT_KEY_1',               'key_1');
if (!defined('GUI_EVENT_KEY_2'))				define('GUI_EVENT_KEY_2',               'key_2');
if (!defined('GUI_EVENT_KEY_3'))				define('GUI_EVENT_KEY_3',               'key_3');
if (!defined('GUI_EVENT_KEY_4'))				define('GUI_EVENT_KEY_4',               'key_4');
if (!defined('GUI_EVENT_KEY_5'))				define('GUI_EVENT_KEY_5',               'key_5');
if (!defined('GUI_EVENT_KEY_6'))				define('GUI_EVENT_KEY_6',               'key_6');
if (!defined('GUI_EVENT_KEY_7'))				define('GUI_EVENT_KEY_7',               'key_7');
if (!defined('GUI_EVENT_KEY_8'))				define('GUI_EVENT_KEY_8',               'key_8');
if (!defined('GUI_EVENT_KEY_9'))				define('GUI_EVENT_KEY_9',               'key_9');

# Adding GUI event key defs
define('GUI_EVENT_KEY_DISCRETE_POWER_ON',					'key_power_on');
define('GUI_EVENT_KEY_DISCRETE_POWER_OFF',					'key_power_off');

# Standby modes
define('STANDBY_MODE_OFF',									0);
define('STANDBY_MODE_ON',									1);

# Video Zoom presets
define('VIDEO_ZOOM_NORMAL',									'normal');
define('VIDEO_ZOOM_ENLARGE',								'enlarge');
define('VIDEO_ZOOM_MAKE_WIDER',								'make_wider');
define('VIDEO_ZOOM_NON_LINEAR_STRETCH',						'fill_screen');
define('VIDEO_ZOOM_NON_LINEAR_STRETCH_TO_FULL_SCREEN',		'full_fill_screen');
define('VIDEO_ZOOM_MAKE_TALLER',							'make_taller');
define('VIDEO_ZOOM_CUT_EDGES',								'cut_edges');
define('VIDEO_ZOOM_FULL_SCREEN',							'full_enlarge');
define('VIDEO_ZOOM_STRETCH_TO_FULL_SCREEN',					'full_stretch');

# Player states
define('PLAYER_STATE_STANDBY',								'standby');
define('PLAYER_STATE_BLACK_SCREEN',							'black_screen');
define('PLAYER_STATE_NAVIGATOR',							'navigator');
define('PLAYER_STATE_FILE_PLAYBACK',						'file_playback');
define('PLAYER_STATE_DVD_PLAYBACK',							'dvd_playback');
define('PLAYER_STATE_BLURAY_PLAYBACK',						'bluray_playback');

# Playback events
define('PLAYBACK_STOPPED',									'stopped');
define('PLAYBACK_INITIALIZING',								'initializing');
define('PLAYBACK_PLAYING',									'playing');
define('PLAYBACK_PAUSED',									'paused');
define('PLAYBACK_SEEKING',									'seeking');
define('PLAYBACK_BUFFERING',								'buffering');
define('PLAYBACK_FINISHED',									'finished');
define('PLAYBACK_DEINITIALIZING',							'deinitializing');
define('PLAYBACK_PCR_DISCONTINUITY',						'pcr_discontinuity');
define('PLAYBACK_MEDIA_OPEN_FAILED',						'media_open_failed');

# Mounted storages path
define('DUNE_MOUNTED_STORAGES_PATH',						'/tmp/mnt/storage/');

# Dune colors consts.
# Common:
define('DEF_LABEL_TEXT_COLOR_BLACK',			0);  #0x000000	Black						IPTV plugin playback time and number of EPG item
define('DEF_LABEL_TEXT_COLOR_BLUE',				1);  #0x0000a0	Blue						unknown
define('DEF_LABEL_TEXT_COLOR_PALEGREEN',		2);  #0xc0e0c0	Light light grin			unknown
define('DEF_LABEL_TEXT_COLOR_LIGHTBLUE',		3);  #0xa0c0ff	Light blue					unknown
define('DEF_LABEL_TEXT_COLOR_RED',				4);  #0xff4040	Red							Symbol R Recorded Channel Kartina TV
define('DEF_LABEL_TEXT_COLOR_LIMEGREEN',		5);  #0xc0ff40	Light green					unknown
define('DEF_LABEL_TEXT_COLOR_GOLD',				6);  #0xffe040	Light yellow				unknown
define('DEF_LABEL_TEXT_COLOR_SILVER',			7);  #0xc0c0c0	Light grey					File browser (right subdescription)
define('DEF_LABEL_TEXT_COLOR_GRAY',				8);  #0x808080	Grey						IPTV plugin playback, categories
define('DEF_LABEL_TEXT_COLOR_VIOLET',			9);  #0x4040c0	Violet						unknown
define('DEF_LABEL_TEXT_COLOR_GREEN',			10); #0x40ff40	Green						VOD description rating(IMDB..)
define('DEF_LABEL_TEXT_COLOR_TURQUOISE',		11); #0x40ffff	Cyan						unknown
define('DEF_LABEL_TEXT_COLOR_ORANGE',			12); #0xff8040	Orange						unknown
define('DEF_LABEL_TEXT_COLOR_MAGENTA',			13); #0xff40ff	Purple						unknown
define('DEF_LABEL_TEXT_COLOR_LIGHTYELLOW',		14); #0xffff40	Light yellow				Widget(time, temp), path (last item), mesagges, IPTV playback (channels number, )
define('DEF_LABEL_TEXT_COLOR_WHITE',			15); #0xffffe0	White						Main color, widget, combobox etc
# Extra:
define('DEF_LABEL_TEXT_COLOR_DARKGRAY',			16); #0x404040	Dark grey					Color buttons,
define('DEF_LABEL_TEXT_COLOR_DIMGRAY',			17); #0xaaaaa0	Grey						Some VOD description text
define('DEF_LABEL_TEXT_COLOR_YELLOW',			18); #0xffff00	Yellow						VOD descr
define('DEF_LABEL_TEXT_COLOR_LIGHTGREEN',		19); #0x50ff50	Green						VOD descr
define('DEF_LABEL_TEXT_COLOR_SKYBLUE',			20); #0x5080ff	Blue						VOD descr
define('DEF_LABEL_TEXT_COLOR_CORAL',			21); #0xff5030	Light red					VOD descr
define('DEF_LABEL_TEXT_COLOR_DARKGRAY2',		22); #0x404040	Dark grey					VOD descr
define('DEF_LABEL_TEXT_COLOR_GAINSBORO',		23); #0xe0e0e0	Light light light grey		P+ P-

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

# Video zoom values for media_url string (|||dune_params|||zoom:value)
class DuneVideoZoomPresets
{
	const	normal						=	'0';
	const	enlarge						=	'1';
	const	make_wider					=	'2';
	const	fill_screen					=	'3';
	const	full_fill_screen			=	'4';
	const	make_taller					=	'5';
	const	cut_edges					=	'6';
	const	full_enlarge				=	'8';
	const	full_stretch				=	'9';
}

# Deinterlacing modes for media_url string (|||dune_params|||deint:value)
class DuneParamsDeintMode
{
	const	off							=	'1';
	const	bob							=	'0';
	const	adaptive					=	'4';
	const	adaptive_plus				=	'5';
}

class DuneIrControl
{
	static $key_codes = array(
			GUI_EVENT_KEY_VENDOR				=> '0',
			GUI_EVENT_KEY_ENTER					=> 'EB14BF00',
			GUI_EVENT_KEY_PLAY					=> 'B748BF00',
			GUI_EVENT_KEY_A_RED					=> 'BF40BF00',
			GUI_EVENT_KEY_B_GREEN				=> 'E01FBF00',
			GUI_EVENT_KEY_C_YELLOW				=> 'FF00BF00',
			GUI_EVENT_KEY_D_BLUE				=> 'BE41BF00',
			GUI_EVENT_KEY_POPUP_MENU			=> 'F807BF00',
			GUI_EVENT_KEY_INFO					=> 'AF50BF00',
			GUI_EVENT_KEY_LEFT					=> 'E817BF00',
			GUI_EVENT_KEY_RIGHT					=> 'E718BF00',
			GUI_EVENT_KEY_UP					=> 'EA15BF00',
			GUI_EVENT_KEY_DOWN					=> 'E916BF00',
			GUI_EVENT_KEY_P_PLUS				=> 'B44BBF00',
			GUI_EVENT_KEY_P_MINUS				=> 'B34CBF00',
			GUI_EVENT_KEY_NEXT					=> 'E21DBF00',
			GUI_EVENT_KEY_PREV					=> 'B649BF00',
			GUI_EVENT_KEY_SETUP					=> 'B14EBF00',
			GUI_EVENT_KEY_RETURN				=> 'FB04BF00',
			GUI_EVENT_KEY_SELECT				=> 'BD42BF00',
			GUI_EVENT_KEY_CLEAR					=> 'FA05BF00',
			GUI_EVENT_KEY_PAUSE					=> 'E11EBF00',
			GUI_EVENT_KEY_FWD					=> 'E41BBF00',
			GUI_EVENT_KEY_REW					=> 'E31CBF00',
			GUI_EVENT_KEY_SLOW					=> 'E51ABF00',
			GUI_EVENT_KEY_STOP					=> 'E619BF00',
			GUI_EVENT_KEY_TOP_MENU				=> 'AE51BF00',
			GUI_EVENT_KEY_POWER					=> 'BC43BF00',
			GUI_EVENT_KEY_EJECT					=> 'EF10BF00',
			GUI_EVENT_KEY_MODE					=> 'BA45BF00',
			GUI_EVENT_KEY_MUTE					=> 'B946BF00',
			GUI_EVENT_KEY_V_PLUS				=> 'AD52BF00',
			GUI_EVENT_KEY_V_MINUS				=> 'AC53BF00',
			GUI_EVENT_KEY_SEARCH				=> 'F906BF00',
			GUI_EVENT_KEY_ZOOM					=> 'FD02BF00',
			GUI_EVENT_KEY_SUBTITLE				=> 'AB54BF00',
			GUI_EVENT_KEY_REPEAT				=> 'B24DBF00',
			GUI_EVENT_KEY_AUDIO					=> 'BB44BF00',
			GUI_EVENT_KEY_REC					=> '9F60BF00',
			GUI_EVENT_KEY_DUNE					=> '9E61BF00',
			GUI_EVENT_KEY_URL					=> '9D62BF00',
			GUI_EVENT_KEY_0						=> 'F50ABF00',
			GUI_EVENT_KEY_1						=> 'F40BBF00',
			GUI_EVENT_KEY_2						=> 'F30CBF00',
			GUI_EVENT_KEY_3						=> 'F20DBF00',
			GUI_EVENT_KEY_4						=> 'F10EBF00',
			GUI_EVENT_KEY_5						=> 'F00FBF00',
			GUI_EVENT_KEY_6						=> 'FE01BF00',
			GUI_EVENT_KEY_7						=> 'EE11BF00',
			GUI_EVENT_KEY_8						=> 'ED12BF00',
			GUI_EVENT_KEY_9						=> 'EC13BF00',
			GUI_EVENT_KEY_SHUFFLE				=> 'B847BF00',
			GUI_EVENT_KEY_KEYBRD				=> 'FC03BF00',
			GUI_EVENT_KEY_MOUSE					=> 'B04FBF00',
			GUI_EVENT_KEY_RECENT				=> '9E61BF00',
			GUI_EVENT_KEY_TV					=> '9C63BF00',
			GUI_EVENT_KEY_MOVIES				=> 'B847BF00',
			GUI_EVENT_KEY_MUSIC					=> 'A758BF00',
			GUI_EVENT_KEY_ANGLE					=> 'B24DBF00',
			GUI_EVENT_KEY_DISCRETE_POWER_ON		=> 'A05FBF00',
			GUI_EVENT_KEY_DISCRETE_POWER_OFF	=> 'A15EBF00');
}


###############################################################################
# System functions
###############################################################################

/**
 * @return string
 */
function get_product_id()
{
	static $result = null;

	if (is_null($result))
	{
		$result = trim(shell_exec('grep "product_id:" /tmp/sysinfo.txt | sed "s/^.*: *//"'));

		if ($app_ver = trim(shell_exec('grep "app_version:" /tmp/sysinfo.txt | sed "s/^.*: *//"')))
			$result .= " ($app_ver)";
	}

	return $result;
}

/**
 * @return boolean
 */
function product_is_apk()
{
	static $result;

	if (is_null($result))
		$result = (!preg_match('/^\/(flashdata|persistfs)\//', DuneSystem::$properties['data_dir_path']) || ($fs_prefix = getenv('FS_PREFIX')));

	return $result;
}
/**
 * @return string
 */
function get_platform_kind()
{
	static $result;

	if (is_null($result))
	{
		$result = trim(shell_exec('grep "platform_kind" /tmp/run/versions.txt | sed "s/^.*= *//"'));

		if (empty($result))
			if (product_is_apk())
				$result = 'android';
	}

	return $result;
}

/**
 * @return string
 */
function get_raw_firmware_version()
{
	static $result;

	if (is_null($result))
		$result = trim(shell_exec('grep "firmware_version:" /tmp/sysinfo.txt | sed "s/^.*: *//"'));

	return $result;
}

/**
 * @return array
 *   (
 *      'string' => '150729_0139_r10_js_stb_opera33',
 *      'build_date' => '150729',
 *      'build_num' => '0139',
 *      'branch_num' => '10',
 *      'features' => 'js_stb_opera33',
 *    )
 */
function get_parsed_firmware_ver()
{
	static $result;

	if (is_null($result))
	{
		preg_match_all('/^(\d*)_(\d*)_\D*(\d*)(.*)$/', get_raw_firmware_version(), $matches, PREG_SET_ORDER);
		$matches[0][4] = ltrim($matches[0][4], '_');
		$result = array_combine(array('string', 'build_date', 'build_num', 'branch_num', 'features'), $matches[0]);
	}

	return $result;
}

/**
 * @return string
 */
function get_serial_number()
{
	static $result;

	if (is_null($result))
		$result = trim(shell_exec('grep "serial_number:" /tmp/sysinfo.txt | sed "s/^.*: *//"'));

	return $result;
}

/**
 * @return string
 */
function get_ip_address()
{
	switch(get_platform_kind())
	{
		case '8670':
			$active_network_connection = parse_ini_file('/tmp/run/active_network_connection.txt', 0, INI_SCANNER_RAW);

			return
				isset($active_network_connection['ip'])? trim($active_network_connection['ip']) : '';

		default:
			$get_inet_addr_cmd = 'ifconfig %s 2>/dev/null | head -2 | tail -1 | sed "s/^.*inet addr:\([^ ]*\).*$/\1/"';
			$inet_addr = trim(shell_exec(sprintf($get_inet_addr_cmd, 'eth0')));

			if (!is_numeric(preg_replace('/\s|\./', '', $inet_addr)))
			{
				$inet_addr = trim(shell_exec(sprintf($get_inet_addr_cmd, 'wlan0')));

				if (!is_numeric(preg_replace('/\s|\./', '', $inet_addr)))
					$inet_addr = 'n/a';
			}
	}

	return $inet_addr;
}

/**
 * @return string|null
 */
function get_mac_address()
{
	switch(get_platform_kind())
	{
		case 'android':
			if (file_exists('/tmp/run/dune_mac.txt'))
				return file_get_contents('/tmp/run/dune_mac.txt');

			if (product_is_apk())
			{
				$get_hw_addr_cmd = 'ifconfig %s 2>/dev/null | head -1 | sed "s/^.*HWaddr //" | grep -Eo "^.{17}"';
				$hw_addr = trim(shell_exec(sprintf($get_hw_addr_cmd, 'eth0')));

				return
					is_numeric(preg_replace('/\s|-/', '', $hw_addr))? $hw_addr : 'n/a';
			}

		default:
			return trim(shell_exec('cat /sys/class/net/eth0/address'));
	}
}

/**
 * @return string
 */
function get_dns_address()
{
	if (get_platform_kind() == 'android')
		$dns = explode(PHP_EOL, shell_exec('getprop | grep "net.dns"'));
	else
		$dns = explode(PHP_EOL, shell_exec('cat /etc/resolv.conf | grep "nameserver"'));

	$addr = '';

	foreach ($dns as $key => $server)
		if (preg_match("|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|", $server, $m))
			$addr .= "nameserver" . ($key + 1) . ": " . $m[1] . ", ";

	return $addr;
}

/**
 * @return int
 */
function get_local_time_zone_offset()
{
	exec((file_exists('/etc/TZ')? 'TZ=`cat /etc/TZ` ' : '') . 'date +%z', $tz, $rc);
	$local_tz = (($rc != 0) || (count($tz) != 1) || !is_numeric($tz[0]))? '' : $tz[0];

	if ($local_tz <> '')
	{
		$sign = 1;
		$sign_ch = substr($local_tz, 0, 1);

		if ($sign_ch == '-')
			$sign = +1;
		else if ($sign_ch == '+')
			$sign = -1;
		else
			return 0;

		$tz_hh = intval(substr($local_tz, 1, 2));
		$tz_mm = intval(substr($local_tz, 3, 2));

		return
			$sign * ($tz_hh * 60 + $tz_mm) * 60;
	}

	return 0;
}

/**
 * @param string simple_node1, simple_node2, ...etc
 * @return array
 * 	 (
 * 	   'app_name' => string {value},
 * 	   'app_caption' => string {value},
 * 	   'app_version' => string {value},
 * 	   'app_version_idx' => string {value},
 * 	   'simple_node1' => mixed {value},
 * 	   'simple_node2' => mixed {value},
 * 	   ...etc
 * 	 )
 */
function get_plugin_info()
{
	if (file_exists(DuneSystem::$properties['install_dir_path'] . '/dune_plugin.xml'))
		if ($plugin_manifest = @file_get_contents(DuneSystem::$properties['install_dir_path'] . '/dune_plugin.xml', true))
			if ($xml = parse_xml_document($plugin_manifest))
			{
				$result =
					array
					(
						'app_name' => strval($xml->name),
						'app_caption' => strval($xml->caption),
						'app_version' => strval($xml->version),
						'app_version_idx' => isset($xml->version_index)? strval($xml->version_index) : '0',
					);

				foreach(func_get_args() as $node_name)
					$result[$node_name] = json_decode(json_encode($xml->xpath("//$node_name")), true);

				return $result;
			}

	throw new Exception("Dune STB API: Plugin manifest not found!");
}

/**
 * @param $key
 * @return false|string|null
 */
function send_ir_code($key)
{
	if (isset(DuneIrControl::$key_codes[$key]))
		shell_exec('echo ' . DuneIrControl::$key_codes[$key] . ' > /proc/ir/button');
	else
		hd_print("Error in class " . get_class($this) . "::" . __FUNCTION__ . "! Code of key '$key' not found in base!");
}

/**
 * @param $key
 * @return string
 */
function send_ir_code_return_status($key)
{
	if (isset(DuneIrControl::$key_codes[$key]))
		return
			rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=ir_code&ir_code=' . DuneIrControl::$key_codes[$key] . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");

	hd_print("Error in class " . get_class($this) . "::" . __FUNCTION__ . "! Code of key '$key' not found in base!");
}

/**
 * @return string PLAYER_STATE_STANDBY|PLAYER_STATE_BLACK_SCREEN|PLAYER_STATE_NAVIGATOR|PLAYER_STATE_FILE_PLAYBACK|PLAYER_STATE_DVD_PLAYBACK|PLAYER_STATE_BLURAY_PLAYBACK
 */
function get_player_state()
{
	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "player_state" | sed -n "s/^.*player_state = /\1/p"'), "\n");
}

/**
 * @return array|false
 */
function get_player_state_assoc()
{
	return
		parse_ini_file('/tmp/run/ext_command.state', 0, INI_SCANNER_RAW);
}

/**
 * @return int STANDBY_MODE_OFF|STANDBY_MODE_ON
 */
function get_standby_mode()
{
	return
		intval(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "player_state" | sed -n "s/^.*player_state = /\1/p"'), "\n") == PLAYER_STATE_STANDBY);
}

/**
 * @param $mode STANDBY_MODE_OFF|STANDBY_MODE_ON
 * @return string
 */
function set_standby_mode($mode)
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=ir_code&ir_code=' . (($mode == STANDBY_MODE_OFF)? 'A05FBF00' : 'A15EBF00') . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @return array (assoc)
 */
function get_shell_settings()
{
	if (file_exists('/config/settings.properties'))
		if ($settings = parse_ini_file('/config/settings.properties', 0, INI_SCANNER_RAW))
			return $settings;

	return array();
}

/**
 * @param $settings_arr (assoc)
 * @return bool
 */
function set_shell_settings($settings_arr)
{
	if (is_array($settings_arr))
	{
		$settings_txt = '';

		foreach($settings_arr as $k => $v)
			$settings_txt .= "$k = $v" . PHP_EOL;

		if (strlen($settings_txt) > 100)
			if (file_put_contents('/config/settings.properties', $settings_txt))
				return true;
	}

	return false;
}

/**
 * @return bool
 */
function cenc_drm_support()
{
	static $result;

	if (get_platform_kind() <> 'android')
		return false;

	if (!is_null($result))
		return $result;

	$home_dir = str_replace('/tmp/plugins/' . DuneSystem::$properties['plugin_name'], '', DuneSystem::$properties['tmp_dir_path']);
	$lib_path = "$home_dir/lib/libduneshell.so";

	if (!file_exists($lib_path))
		$lib_path = '/system/priv-app/dunehd_shell/lib/arm/libduneshell.so';

	if (file_exists($lib_path))
		$result = (strpos(file_get_contents($lib_path), 'decryption_key') !== false);
	else
	{
		$result = false;
		hd_print("Error CENC detecting: \"$lib_path\" not found!");
	}

	return $result;
}

###############################################################################
# Playback controls
###############################################################################

/**
 * @return string PLAYBACK_STOPPED|PLAYBACK_INITIALIZING... etc
 */
function get_playback_state()
{
	# return string

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_state" | sed -n "s/^.*playback_state = /\1/p"'), "\n");
}

/**
 * @return string (command execution status)
 */
function stop()
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=light_stop" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @return bool
 */
function can_pause()
{
	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "pause_is_available" | sed -n "s/^.*pause_is_available = /\1/p"'), "\n") == 1);
}

/**
 * @return string (command execution status)
 */
function pause()
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&speed=0" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @return string (command execution status)
 */
function resume()
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&speed=256" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @return string    (-8192|-4096|-2048|-1024|-512|-256| -128|  -64|  -32|   -16|    -8|    0|    8|   16|   32|  64| 128|  256|  512| 1024| 2048|  4096|  8192)
 * corresponds speed ( -32x| -16x|  -8x|  -4x| -2x| -1x|-1/2x|-1/4x|-1/8x|-1/16x|-1/32x|pause|1/32x|1/16x| 1/8x|1/4x|1/2x|   1x|   2x|   4x|   8x|   16x|   32x)
 */
function get_speed()
{
	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_speed" | sed -n "s/^.*playback_speed = /\1/p"'), "\n");
}

/**
 * @param $value    (-8192|-4096|-2048|-1024|-512|-256| -128|  -64|  -32|   -16|    -8|    0|    8|   16|   32|  64| 128|  256|  512| 1024| 2048|  4096|  8192)
 * corresponds speed ( -32x| -16x|  -8x|  -4x| -2x| -1x|-1/2x|-1/4x|-1/8x|-1/16x|-1/32x|pause|1/32x|1/16x| 1/8x|1/4x|1/2x|   1x|   2x|   4x|   8x|   16x|   32x)
 * @return string (command execution status)
 */
function set_speed($value)
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&speed=' . $value . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @return string
 */
function get_length_seconds()
{
	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_duration" | sed -n "s/^.*playback_duration = /\1/p"'), "\n");
}

/**
 * @return bool [true if length of media is known]
 */
function has_length()
{
	return
		(get_length_seconds() > 0);
}

/**
 * @return string
 */
function get_position_seconds()
{
	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_position" | sed -n "s/^.*playback_position = /\1/p"'), "\n");
}

/**
 * @param $seconds
 * @return string (command execution status)
 */
function set_position_seconds($seconds)
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=' . (empty($seconds)? 'status' : 'set_playback_state&position=' . $seconds) . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @param $speed
 * @param $seconds
 * @return string (command execution status)
 */
function set_speed_and_position_seconds($speed, $seconds)
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=' . ((empty($speed) || empty($seconds))? 'status' : 'set_playback_state&speed=' . $speed . '&position=' . $seconds) . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @return bool
 */
function is_scrambling_detected()
{
	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "scrambling_detected" | sed -n "s/^.*scrambling_detected = /\1/p"'), "\n") == 1);
}

/**
 * @return string [length of one media segment for segment-based media, for HLS returns the value of X-EXT-TARGETDURATION]
 */
function get_segment_length_seconds()
{
	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "segment_length" | sed -n "s/^.*segment_length = /\1/p"'), "\n");
}

/**
 * @return string
 */
function get_playback_url()
{
	$url = rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_url" | sed -n "s/^.*playback_url = /\1/p"'), "\n");

	if (preg_match_all('/\/\/(127\.0\.0\.1|localhost).*((htt|rt|rtc|rts|ud)p:\/\/.*$)/i', $url, $matches))
		if (!empty($matches[2][0]))
			return $matches[2][0];

	return $url;
}


###############################################################################
# Audio controls
###############################################################################

/**
 * @return string [0..100]
 */
function get_volume()
{
	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_volume" | sed -n "s/^.*playback_volume = /\1/p"'), "\n");
}

/**
 * @param $percent [0..100]
 * @return string (command execution status)
 */
function set_volume($percent)
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&volume=' . $percent . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @param bool
 * @return string (command execution status)
 */
function is_mute_enabled()
{
	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_mute" | sed -n "s/^.*playback_mute = /\1/p"'), "\n") == 1);
}

/**
 * @return string (command execution status)
 */
function enable_mute()
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&mute=1" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @return string (command execution status)
 */
function disable_mute()
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&mute=0" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

/**
 * @return array
 * 	 (
 * 	 [first_track_index] =>
 * 	   array
 *     (
 * 	     'lang' => [value],
 * 	     'pid' => [value],
 * 	     'codec' => [value],
 * 	     'type' => [value],
 *     ),
 * 	 [next_track_index] =>
 * 	   array
 *     (
 * 	     'lang' => [value],
 * 	     'pid' => [value],
 * 	     'codec' => [value],
 * 	     'type' => [value],
 *     ),
 * 	 ...
 * 	 )
 */
function get_audio_tracks_description()
{
	preg_match_all('/audio_track\.(\d)\.(.*)\s=\s(.*$)/mx', file_get_contents('/tmp/run/ext_command.state'), $matches);

	$result = array();

	foreach($matches[1] as $key => $value)
		$result[$value][$matches[2][$key]] = $matches[3][$key];

	return $result;
}

/**
 * @return string [0..N] (current audio track index)
 */
function get_audio_track()
{
	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "audio_track" | sed -n "s/^.*audio_track = /\1/p"'), "\n");
}

/**
 * @param $track [0..N] (audio track index)
 * @return string (command execution status)
 */
function set_audio_track($track)
{
	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&audio_track=' . $track . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}


###############################################################################
# Teletext controls (playback in TV mode)
###############################################################################

function is_teletext_available()
{
	# Return: boolean value of teletext available in the current stream

	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "teletext_available" | sed -n "s/^.*teletext_available = /\1/p"'), "\n") == 1);
}

function is_teletext_enabled()
{
	# Return: boolean value of teletext mode is turned

	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "teletext_enabled" | sed -n "s/^.*teletext_enabled = /\1/p"'), "\n") == 1);
}

function enable_teletext()
{
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&teletext_enabled=1" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function disable_teletext()
{
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&teletext_enabled=0" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function get_teletext_page_number()
{
	# Return: string value of current teletext page number

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "teletext_page_number" | sed -n "s/^.*teletext_page_number = /\1/p"'), "\n");
}

function set_teletext_page_number($value)
{
	# Argument: 100..899 - page number
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&teletext_page_number=' . $value . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function is_teletext_mix_mode_enabled()
{
	# Return: boolean value of teletext mix mode is turned

	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "teletext_mix_mode" | sed -n "s/^.*teletext_enabled = /\1/p"'), "\n") == 1);
}

function enable_teletext_mix_mode()
{
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&teletext_mix_mode=1" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function disable_teletext_mix_mode()
{
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&teletext_mix_mode=0" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}


###############################################################################
# Video controls
###############################################################################

function is_video_enabled()
{
	# Returns true if primary video showing during primary video playback is enabled.
	# When primary video playback is performed and primary video showing is disabled,
	# primary video playback internally runs in the usual way, but primary video is
	# hidden. By default, primary video showing is enabled.

	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "video_enabled" | sed -n "s/^.*video_enabled = /\1/p"'), "\n") == 1);
}

function enable_video()
{
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&video_enabled=1" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function disable_video()
{
	# Disables showing of primary video when primary video playback is running. Does
	# not affect primary video playback itself (i.e. the primary video playback
	# internally continues, only primary video becomes hidden).
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&video_enabled=0" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function get_video_zorder()
{
	# Return: string value of current Z-order of primary video

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "video_zorder" | sed -n "s/^.*video_zorder = /\1/p"'), "\n");
}

function set_video_zorder($value)
{
	# Sets Z-order of primary video. If primary video playback is not running or primary
	# video showing is disabled, primary video layer is absent and this setting has no
	# effect. Z-ordering of primary video, PiP video and OSD layers works in the following
	# way. Layers with greater Z-order are put above layers with lesser Z-order. Default
	# Z-order values for primary video, PiP video, OSD are: 200, 400, 500. If some of the
	# layers (primary video, PiP video, OSD) have the same Z-order, the following ordering
	# rules are used: OSD is above PiP video, PiP video is above primary video.
	# Argument: 0..1000 - Z-order
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&video_zorder=' . $value . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function get_osd_zorder()
{
	# Return: string value of current Z-order of OSD

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "osd_zorder" | sed -n "s/^.*osd_zorder = /\1/p"'), "\n");
}

function set_osd_zorder($value)
{
	# Argument: 0..1000 - Z-order
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&osd_zorder=' . $value . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function is_video_on_top()
{
	# Return: true if primary video has Z-order greater than Z-order of OSD

	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "video_on_top" | sed -n "s/^.*video_on_top = /\1/p"'), "\n") == 1);
}

function enable_video_on_top()
{
	# Puts primary video above OSD. The function is equivalent to setVideoZOrder(900) and
	# setOSDZOrder(500). NOTE: This function is intended to be used by applications which
	# do not use PiP video; if PiP video is used, it is recommended not to use this function
	# and instead use set{Video,PIP,OSD}ZOrder functions directly.
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&video_on_top=1" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function disable_video_on_top()
{
	# Puts primary video below OSD. The function is equivalent to setVideoZOrder(200) and
	# setOSDZOrder(500)). NOTE: This function isintended to be used by applications which
	# do not use PiP video; if PiP video is used, it is recommended not to use this function
	# and instead use set{Video,PIP,OSD}ZOrder functions directly.
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&video_on_top=0" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function is_window_full_screen()
{
	# Return: boolean value of window full screen mode enabled

	return
		(rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_window_fullscreen" | sed -n "s/^.*playback_window_fullscreen = /\1/p"'), "\n") == 1);
}

function enable_window_full_screen()
{
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&window_fullscreen=1" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function get_window_rect_x()
{
	# Return: string value of window rect x

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_window_rect_x" | sed -n "s/^.*playback_window_rect_x = /\1/p"'), "\n");
}

function get_window_rect_y()
{
	# Return: string value of window rect y

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_window_rect_y" | sed -n "s/^.*playback_window_rect_y = /\1/p"'), "\n");
}

function get_window_rect_width()
{
	# Return: string value of window rect width

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_window_rect_width" | sed -n "s/^.*playback_window_rect_width = /\1/p"'), "\n");
}

function get_window_rect_height()
{
	# Return: string value of window rect height

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_window_rect_height" | sed -n "s/^.*playback_window_rect_height = /\1/p"'), "\n");
}

function set_window_rect($x, $y, $width, $height)
{
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&window_fullscreen=0&window_rect_x=' . $x . '&window_rect_y=' . $y . '&window_rect_width=' . $width . '&window_rect_height=' . $height . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function get_clip_rect_x()
{
	# Return: string value of clip rect x

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_clip_rect_x" | sed -n "s/^.*playback_clip_rect_x = /\1/p"'), "\n");
}

function get_clip_rect_y()
{
	# Return: string value of clip rect y

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_clip_rect_y" | sed -n "s/^.*playback_clip_rect_y = /\1/p"'), "\n");
}

function get_clip_rect_width()
{
	# Return: string value of clip rect width

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_clip_rect_width" | sed -n "s/^.*playback_clip_rect_width = /\1/p"'), "\n");
}

function get_clip_rect_height()
{
	# Return: string value of clip rect height

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_clip_rect_height" | sed -n "s/^.*playback_clip_rect_height = /\1/p"'), "\n");
}

function set_clip_rect($x, $y, $width, $height)
{
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&clip_rect_x=' . $x . '&clip_rect_y=' . $y . '&clip_rect_width=' . $width . '&clip_rect_height=' . $height . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function get_video_source_rect_x()
{
	# Return: string value of video source rect x

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_video_source_rect_x" | sed -n "s/^.*playback_video_source_rect_x = /\1/p"'), "\n");
}

function get_video_source_rect_y()
{
	# Return: string value of video source rect y

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_video_source_rect_y" | sed -n "s/^.*playback_video_source_rect_y = /\1/p"'), "\n");
}

function get_video_source_rect_width()
{
	# Return: string value of video source rect width

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_video_source_rect_width" | sed -n "s/^.*playback_video_source_rect_width = /\1/p"'), "\n");
}

function get_video_source_rect_height()
{
	# Return: string value of video source rect height

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_video_source_rect_height" | sed -n "s/^.*playback_video_source_rect_height = /\1/p"'), "\n");
}

function set_video_source_rect($x, $y, $width, $height)
{
	# Sets coordinates of a portion of the original video for displaying in window or on full
	# screen as specified via set_window_rect(). The specification of the portion coordinates is
	# in 0-4096 scale for all arguments instead of OSD size scale in set_window_rect() and
	# set_clip_rect() thus set_video_source_rect(0, 0, 4096, 4096) specifies complete original video.
	# The coordinates are specified relative to the original video dimensions with no dependence
	# on display/window aspect ratio so if there is, say, 4:3 video that should be displayed in a
	# 3:1 window and set_video_source_rect(0, 0, 1024, 4096) is called then the resulting video will
	# have 1:3 aspect ratio with appropriate black bars added in the window if normal zoom mode is
	# specified. The video source rectangle specification is also compatible with
	# VIDEO_ZOOM_STRETCH_TO_FULL_SCREEN preset, in this case the portion of the original video is
	# stretched to full screen or window as expected. Other zoom presets are not compatible, just
	# like with set_clip_rect() call - set_video_source_rect() fails if an incompatible zoom preset is
	# set and set_video_zoom() fails with an incompatible zoom preset if a partial video source
	# rectangle is already in effect. Note that both video source rectangle and clip rectangle may
	# be specified with set_video_source_rect() and set_clip_rect() respectively but if both are in effect
	# then the clip rectangle setting is ignored until set_video_source_rect() is set to complete
	# original video so programr should take care to avoid such situations.
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&video_source_rect_x=' . $x . '&video_source_rect_y=' . $y . '&video_source_rect_width=' . $width . '&video_source_rect_height=' . $height . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

function get_video_width()
{
	# Return: string value of current video width

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_video_width" | sed -n "s/^.*playback_video_width = /\1/p"'), "\n");
}

function get_video_height()
{
	# Return: string value of current video height

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "playback_video_height" | sed -n "s/^.*playback_video_height = /\1/p"'), "\n");
}

function get_video_zoom()
{
	# Return: string value of current video zoom

	return
		rtrim(shell_exec('cat /tmp/run/ext_command.state | grep -w "video_zoom" | sed -n "s/^.*video_zoom = /\1/p"'), "\n");
}

function set_video_zoom($value)
{
	# Argument: VIDEO_ZOOM_NORMAL | VIDEO_ZOOM_ENLARGE | VIDEO_ZOOM_MAKE_WIDER | VIDEO_ZOOM_NON_LINEAR_STRETCH |
	#           VIDEO_ZOOM_NON_LINEAR_STRETCH_TO_FULL_SCREEN | VIDEO_ZOOM_MAKE_TALLER | VIDEO_ZOOM_CUT_EDGES |
	#           VIDEO_ZOOM_FULL_SCREEN | VIDEO_ZOOM_STRETCH_TO_FULL_SCREEN
	# Return: command execution status

	return
		rtrim(shell_exec('env REQUEST_METHOD="GET" QUERY_STRING="cmd=set_playback_state&video_zoom=' . $value . '" /firmware/ext_command/cgi-bin/do | grep "command_status" | sed -n "s/^<param name=\"command_status\" value=\"\(.*\)\"\/>/\1/p"'), "\n");
}

###############################################################################
# Storage access
###############################################################################

function get_local_storages_list()
{
	# Return: array of local storages

	$i = 0;
	$result = array();

	foreach(scandir(DUNE_MOUNTED_STORAGES_PATH) as $item)
	{
		if (($item == '.') || ($item == '..') || !is_dir(DUNE_MOUNTED_STORAGES_PATH . $item))
			continue;

		$disk_name = '';

		foreach(explode('_', $item) as $chunk)
			$disk_name .= is_numeric('0x' . $chunk)? '' : $chunk;

		$aliase = (($disk_name == 'DuneHDD')? 'D:' : 'usb' . $i++ . ':');
		$result['list'][$item] = $aliase;
		$result['names'][] = $item;
		$result['aliases'][] = $aliase;
		$result['labels'][] = (($disk_name == 'usbstorage')? '' : $disk_name);
	}

	return $result;
}

###############################################################################
# HTTP Functions
###############################################################################

function get_client_extra_info()
{
	# Returns a string from the hardware ID and firmware version

	static	$client_extra_info = null;

	if (is_null($client_extra_info))
	{
		$client_extra_info = '';

		if (file_exists('/tmp/sysinfo.txt') && ($sys_info = file('/tmp/sysinfo.txt', FILE_IGNORE_NEW_LINES)))
		{
			foreach ($sys_info as $line)
				if (preg_match('/product_id:|firmware_version:/', $line))
					$client_extra_info .= (empty($client_extra_info)? ' (' : ', ') . $line;

			$client_extra_info .= empty($client_extra_info)? '' : ') ';
		}
	}

	return $client_extra_info;
}

function http_get_document($url, $opts = null, $silent = false)
{
	if (file_exists($url))
		return file_get_contents($url);

	$ch = curl_init();

	curl_setopt($ch, CURLOPT_RETURNTRANSFER,    true);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION,	true);
	curl_setopt($ch, CURLOPT_TIMEOUT,           20);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,    6);
	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,	false);
	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,	false);
	curl_setopt($ch, CURLOPT_USERAGENT,         'DuneHD/1.0' . get_client_extra_info());
	curl_setopt($ch, CURLOPT_URL,               $url);

	if (is_array($opts))
		foreach ($opts as $k => $v)
			curl_setopt($ch, $k, $v);

	if (!$silent)
		hd_print("HTTP fetching '$url'...");


	$content = curl_exec($ch);
	$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

	if ($content === false)
		hd_print("HTTP error: $http_code; " . "CURL errno: " . curl_errno($ch) . " (" . curl_error($ch) . ')');

	curl_close($ch);

	if ($http_code != 200)
	{
		hd_print("HTTP request failed ($http_code)");
		return null;
	}
	else if (!$silent)
		hd_print("HTTP OK ($http_code)");

	return $content;
}

function http_post_document($url, $post_data, $opts = null, $silent = false)
{
	$header = array(
		CURLOPT_POST => true,
		CURLOPT_POSTFIELDS => $post_data);

	if (isset($opts))
		foreach ($opts as $key => $value)
		   $header[$key] = $value;

	return
		http_get_document($url, $header, $silent);
}

###############################################################################
# XML Docs
###############################################################################

function parse_xml_document($doc)
{
	$xml = simplexml_load_string($doc, null, LIBXML_NOCDATA | LIBXML_NOEMPTYTAG);

	if ($xml === false) {
		hd_print("Error: can not parse XML document.");
		hd_print("XML-text: $doc.");
		hd_print('Dune STB API: Illegal XML document');
		return false;
	}

	return $xml;
}

###############################################################################
# Miscellaneous
###############################################################################

function get_active_skin_path()
{
	# Returns the path to the directory of the active skin (no trailing slash)

	if (file_exists('/tmp/dune_skin_dir.txt'))
		return
			preg_replace('/^(.*)\/(flashdata|persistfs).*/', '$1', DuneSystem::$properties['data_dir_path']) . rtrim(trim(preg_replace('/^.*=/', '', file_get_contents('/tmp/dune_skin_dir.txt'))), '/');

	hd_print("Error in class " . get_class($this) . "::" . __FUNCTION__ . "! Can not determine the path to the active skin.");
}

function get_paved_path($path, $dir_mode = 0777)
{
	# Returns the specified path (no trailing slash), creating directories along the way

	if (!file_exists($path))
		@mkdir($path, $dir_mode, true);

	return rtrim($path, '/');
}

function decode_str($str, $offset = 2)
{
	return
		empty($str)? '' : base64_decode(substr($str, $offset) . '=');
}

function json_encode_unicode($data)
{
	# Analog of json_encode() with the JSON_UNESCAPED_UNICODE option available in PHP 5.4.0 and higher

	array_walk_recursive(
		$data,
		function(&$item, $key)
		{
			if (is_string($item))
				$item = mb_encode_numericentity($item, array(0x80, 0xffff, 0, 0xffff), 'UTF-8');
		}
	);

	return
		mb_decode_numericentity(json_encode($data), array(0x80, 0xffff, 0, 0xffff), 'UTF-8');
}

function get_canonize_string($str, $encoding = 'UTF-8')
{
	# Returns canonized string in lowercase

	return
		str_replace(
			array('а','в','е','к','м','н','о','р','с','т','у','х'),
			array('a','b','e','k','m','h','o','p','c','t','y','x'),
			mb_strtolower(trim($str), $encoding));
}

/**
 * @param $string_key
 * @return string [constant in the system language by key]
 */
function get_system_language_string_value($string_key)
{
	if ($sys_settings = parse_ini_file('/config/settings.properties', false, INI_SCANNER_RAW))
	{
		$sys_lang = file_exists('/firmware/translations/dune_language_' . $sys_settings['interface_language'] . '.txt')? $sys_settings['interface_language'] : 'english';

		if (($lang_txt = file_get_contents("/firmware/translations/dune_language_$sys_lang.txt")) && preg_match("/^$string_key\\s*=(.*)$/m", $lang_txt, $m))
			return trim($m[1]);
	}

	hd_print("Error in class " . get_class($this) . "::" . __FUNCTION__ . "! Not found value for key '$string_key'!");

	return '';
}

/**
 * @param mixed $var1, $var2, ...etc [printed vars]
 */
function debug_print()
{
	if (!is_null($backtrace = debug_backtrace(false)))
	{
		$var = $chain = '';

		foreach(func_get_args() as $value)
			$var .= "\n" . trim(var_export($value, true), "'");

		for($i = count($backtrace) - 4; $i > 1; $i--)
			if (isset($backtrace[$i-1]['class']))
			{
				if ($backtrace[$i-1]['class'] <> 'UserInputHandlerRegistry')
					$chain .= $backtrace[$i-1]['class'] . '::'.$backtrace[$i-1]['function'] . '()->';
			}
			else
				$chain .= $backtrace[$i-1]['function'] . '()->';

		hd_print('Debug alert! ' . rtrim($chain, '->') . (empty($var)? '' : ' >> ') . ltrim($var, "\n"));
	}
}

?>