<?php
/* ****************************************************************************
	MPDWEB Serie UPnP WebUi : (本体は他に移植し易いように1本で作成)

	Used
	 http://{host}/mpdweb/mpdupnp.php

	起動方法 http://{host}/mpddirettamp.php{?frame=on}{&makessdp=on|off}

	frame=on        : Frameで使用するときでCloseBotton表示
	makessdp=on|off : ssdp castのResultFileを使用 on:する/off:しない (Default:on)

	1.GetCommonConfig():$G_MAKESSDP($makessdp):"on"のときSSDP Requset ResultFileを作成し使用する。(先々設定ファイルを使用する予定)
	・GetCommonConfig():$G_MAKESSDP:"on"
	  初めて実行するとき,SSDP Requset ResultFile "mpdupnp_config.txt"がないときさ作成する。
		  FileSizeが20Byte以下はファイルがないものと扱う。(事前にFileを作成しておきchmod 0666 mpdupnp_config.txtしておく)
	  FilePathは,mpdupnp.phpの存在Path以下にsettingがあるとき
	   setting/mpdupnp_config.txt
	   ない場合,mpdupnp.php直下に作成する
	・GetCommonConfig():$G_MAKESSDP:"off"
	  毎回SSDP Requset を行う

	2.ServerPlayはmsg_get_queue/msg_receiveでProsess間通信(System V IPC使用)を行う。同じRendererで後操作を生かし他をKillする。
	  php.ini : extension=sysvmsg[php内で設定しているextension_loaded('sysvmsg')] $ php -m | grep sysvmsg(sysvmsg.so)で確認
	            ArchLinuxは公式リポジトリに php-sysvmsg パッケージが存在しないので制限付きでの実行となる。
      ServerPlay時Kill要求がだせないので、StopTimerでServerProcessが終了する。手動"Stop"操作を行うか，10秒($G_COMSEC$G_COMSEC)待つと終了する。
	  ※FFW(PLAYED)を多用して、偶々STOPPEDとなった場合、"STOPPED"になる可能性があるSLEEPとFFWの誤差20秒($SVR_STOPREVISE)を保証SECとしている。
	3,本WebUIの動機:UPnP(DLNA)WebUI WebでUPnPコントローラがネット上で他に見つからないので作成することにした。
	  UpMpdCli，幾つかの機種A/V再生を確認する。

	※使用に際して注意
	・DIGA/WMSなどを使用時：設定でMacaddressを許可すること。
	・ socket_createで失敗する場合は
	   /etc/php/php.ini Dynamic Extensions Section : extension=sockets
	   設定されていないと失敗するのでチェックする
	   # systemctl restart php-fpm

	4,コンテンツ表示再生対応機器(制約含む) R:Renderer,C:Contents
	  REGZA42Z2(R),REGZAS9400(S),DIGA:DMR830(C),PionnierBDP-160(R),MarantzCR612(R),WMS(C),BMS2(C),OPPO UD-203(R),LS-WXLB96(C)
	  Linux:UpMpdCli(R/C),Gerbera(C),Mediatomb(C),miniDLNA(C)
	5.問題点)UPnPはメーカごとが多すぎる
	  DIGA , REGZA    : コンテンツ取得に時間が掛かるとき，Time以外で切断される。再度クリックで取得する。
	  REGZA 42Z2      ; 再生コンテンツを選択したとき前の動画を再生してしまう。 ※ metaClear後，Playすれば良いが,未実装
	  Pionnier BDP    : コンテンツタイトルが設定されない。操作Start/Stop以外は不明。
	  Squeeze2upnp/LMS: squeeze2upnp/LMSでBridgeされたLMS、件数カウントがない。(内部でカウント)する)
	  UpmMdCli/Qobuz  : 件数カウントがない。(内部でカウント)する)
	  Marantz M-CR612 : (AIOSDevice) Meta情報(全てではない)は設定できるようになった。(originalTrackNumber セットできず)
	                    連続再生が止まり易い(SmartPhone版があるので調査せず)。
                        WIFI環境しかないが、ハイレゾが転送中で止まるよう。(専用SoftがOKなのでTimeout/Bufferの取り方?)
	  OPPO UDP-203    : DIGA VideoがPlayできない
	  ※HeOSはMarantzで判定しているが多分Denonも同様と思われる。(バージョンアップ/ファームアップで変わるが未対応のままとする)
	  Music再生時、AlbumArtはRendererに送信してるが対応はRenderer次第。
	  コンテンツ数が多すぎると，取り切れていない。(件数Maxを実装)
	6.標準での動作 Contents Select&Play
	                  : Windows BMS / OPPO UD-203 / UpMpdCli / REGZA 42Z2 / LS-WXLB96
	7,対応予定
	  安定性の向上
	  RendererがmpdのときListPlayにQueueを送出する。
	  Album Artの表示をスレッド数毎にする。(一気に表示は付加が発生する)
	8.対応予定なし
	  Contents数が多すぎる場合は表示しない。
	  URLで渡されたMusicコンテンツをファイルはすれば,Queueと安定化になるが現在考えていない。
	9,発見したこと
	  Renderer   : Stop,Startが必要なケースが多い
	  DIGA       : <s:Body><TotalMatches>:取得件数,<TotalContents>合計を見て，<StartingIndex>を設定して取得
	               urn:schemas-upnp-org～:ContentDirectory:2'でContentCotentControleを設定している
	  AIOSDevice : urn:schemas-denon-com:device:AiosDevice:1 でRenderer AVTransportを設定している
	               Actionには Header:"SOAPAction: "urn:schemas-upnp-org:service:{service}1#{action}"が必要
	               DLNA規定のTagMetaした場合、Rendererからの応答にはMetaがあるが、実機に表示方法不明。
	  Pioneer BDP: 機種限定と思われるが，<s:Body>はエラーとなる<soap:Body>で可。<soapのみ省略形はエラーとなる。Album再生はできない。
	  minidlna   : "\a"が含まれるケースがある(minidlna改行のバグ?)ステータスとの関連が不明,RelTimeがと入れない場合はあった)
	  Qobuz      : ストリーミングにはMetatagが含まれていない。(upmpdcliの応答がない場合がある)
	  Windows:WMS: SOAPRequest;Header:「GET {Paramater含む(通常はURL部のみ)} HTTP/1.1\r\n」。
	               Rresponse:<serviceList>より前の形式が異なるXML以外を含む，またTagが異なる。
	  DMR REGZA(ｽﾏﾎdeﾚｸﾞｻﾞ):外部持出 Response<Result></Result>間にゴミがある。(XMLとしては一概には間違いと言えない形式)
	  MinimServer: Transfer-Encoding:chunkedの対応が必要 :hexdec(substr($string, 0, $pos);
	  file_get_contentsはSOAPRequest;Headerは各ケースに対応度が高い。(RresponseはServerによる)
	  DBR-4KZ600(ｽﾏﾎdeﾚｸﾞｻﾞ):XML<Result>タグの最後に不明文字列があるので展開エラー。<DIDL-Liteを採用しXML展開する。
    ※Serverでの連続Play(Browserを終了してもPlay):そのうち組み込む
	  $_POST['p']=="serverplay" :PostUPnPQueueReq()でEvent
	  Jsp:EventNowMusic_Next():連続再生時:targetDiv.click();を実行しないことでできる。
	  Play PointからSearchするのでBrowserで割り込んでも、ある程度は追従する。
***************************************************************************** */

GetCommonConfig();							// Get共通設定
/* -----------------------------------------------------------------------------
	GetCommonConfig()で固定値としているが,直接設定するかFile化で機能性が上がる。

	拡張モジュールも設定している
	mpdupnp.php(本モジュール)と同じServer下で動作しているモジュール:Functionから起動
	  mpdcliconf.php   ; Edit:upmpdcli.conf mpd&Friendly_name&Qobuz
	  mpdconfig.php    : Edit:mpd.conf ALSA Device,Audio_Output
	SSDPでminiDLNAを検知したServer下にmpdminidlnadb.php(mpdupnp.phpを数台に設定)があるとき拡張する。Contents Headerから起動
	  mpdminidlnadb.php: miniDLNA D/B Update & minidlna.conf:Friendly_name
	mpdwebと切り離して使用する場合の支援ツールとしている。単独使用の場合の機能
	使用環境
	  設定を変更するために'su'の実行が必要になる。
	  webからの実行を許可するか、その場合のpasswordなしでの実行するか設定すること。
	  passwordなしでも、支援ツールは,'su'のPasswordを要求するが、未入力で行う。
	※ これら設定ツールはmpdsweb/settingから起動動作はmpdの動作しているサーバとなる部分が異なる。
----------------------------------------------------------------------------- */
ini_set("memory_limit", "160M");			// PHP Max Memory Size String/Array/XML/Json
ini_set('pcre.backtrack_limit',"6M");		// PHP Regular Expression Matching (memory_limitとは関係ない)

//ini_set('display_errors',1);				// エラー表示設定

$MY_VER_UPNP=basename(__FILE__,"")." 0.9 (20.11.2025)";
/* ****************************************************************************
	$_POST Process
***************************************************************************** */
if (isset($_POST['p'])) {
	if (!headers_sent()) {	   // レスポンス形式を宣言
		header("Content-Type: text/plain; charset=UTF-8");
		}
	if($_POST['p']=="upnpmanager") {					// Get Renderer Device Mode
		 PostUpnpManager();
		 }
	else if($_POST['p']=="upnpget") {					// Get UPnP Get Device
		 PostUpnpGet();
		 }
	else if($_POST['p']=="upnpcontents") {				// Get UPnP Contents
		 PostUpnpContents();
		 }
	else if($_POST['p']=="upnpreq") {					// UPnP Play(File)
		 PostUPnPRequest();
		 }
	else if($_POST['p']=="upnpqueue") {					// GUPnP Play(Directory)
		 PostUPnPQueueReq();
		 }
	else if($_POST['p']=="nowplay") {					// nowplay Contents
		 PostNowPlayRequest();
		 }
	else if($_POST['p']=="playpanel") {					// playpanel
		 PostPlayPanel();
		 }
	else if($_POST['p']=="serverplay") {				// Serverで連続Play:PostUPnPQueueReq() 終了前にFunction:RequestByPost()でEvent
		 PostQueueNonStop();			// 精度は低くなるがBrowserを終了しても再生できる。
		 }
	exit;
	}

/* -----------------------------------------------------------------------------
	PostUpnpManager() : connectionManager Request
	$_POST['url']:Renderer ControleURL
	HTML:"idSelImage"に Image使用可のとき"on",未対応時:"off"をセット
----------------------------------------------------------------------------- */
function PostUpnpManager() {
	if(isset($_POST['url'])) {
		$controlUrl=$_POST['url'];
		}
	print "Post UpnpManager Start\n";

	$params = array();
	$response = sendUpnpRequest($controlUrl,"ConnectionManager","GetProtocolInfo",$params);

	if ($response) {
	list($header, $body) = explode("\r\n\r\n", $response, 2);
    $xml = simplexml_load_string($body);
	if ($xml === false) {
		echo "XML parse error\n";
		}
	else{
		$xml->registerXPathNamespace('s', 'http://schemas.xmlsoap.org/soap/envelope/');
		$xml->registerXPathNamespace('u', 'urn:schemas-upnp-org:service:ConnectionManager:1');
		$sinkNodes = $xml->xpath('//u:GetProtocolInfoResponse/Sink');
		if ($sinkNodes && count($sinkNodes) > 0) {
			$sinkStr = (string)$sinkNodes[0];
			$protocols = explode(',', $sinkStr);
			$canAudio = $canVideo = $canImage = false;
			foreach ($protocols as $proto) {
				if (stripos($proto, 'audio/') !== false) $canAudio = true;
				if (stripos($proto, 'video/') !== false) $canVideo = true;
				if (stripos($proto, 'image/') !== false) $canImage = true;
				}
			$capabilities = array(
					'audio' => $canAudio,
					'video' => $canVideo,
					'image' => $canImage,
					);
				}
			}
		}
	if ((isset($capabilities["image"]))&&($capabilities["image"]===true)) {
		print "Function:document.getElementById('idSelImage').textContent='on';\n";		// RendererImage Onを設定する
		}
	else{
		print "Function:document.getElementById('idSelImage').textContent='off';\n";	// RendererImage Onを設定する
		}
	print "Post UpnpManager End\n";
	}

/* -----------------------------------------------------------------------------
	PostUpnpGet() : Get UPnP Get Device
	$_POST['getmode']  : 1:Make UPnPDeviceList , 2:Read UPnPDeviceList
----------------------------------------------------------------------------- */
function PostUpnpGet() {
	global $G_RENDERHEIGHT;

	print "Post Upnp Get Devaive Start\n";
	$Content=array();		// Content  Array:["location"]/["deviceType"]/["deviceType"]/["services"]:array
	$Renderer=array();		// Renderer Array:["location"]/["deviceType"]/["deviceType"]/["services"]:array
	$getmode=$_POST['getmode'];
	getUPnPDevices($Content,$Renderer,$getmode);
	print "idDetaile:";			// Post時 id
	DispUPnPDetaile($G_RENDERHEIGHT,$Content,$Renderer);
	print "\n";
	if($getmode !=="2") {
		 print "Function:EventRendererClear();\n";
		 }
	else {
		 print "Function:EventRendererRowSet();\n";
		 }
	print "Post Upnp Get Devaive End\n";
    flush();
    ob_flush();
	}

/* -----------------------------------------------------------------------------
	PostUpnpContents() : Get UPnP Contents
	$_POST['url']  : ssdpUrl
	$_POST['id']   : ssdp id
	$_POST['title']: Parent Title
	$_POST['bid']  : Return Parent id
	$_POST['cname']: Contents Server Name
	$_POST['image']: Rendererがimage対応かのI/F Rendererが選択されている場合 on/off Rendererが選択されていない場合""
	※ cname=Server+"|"+Name; ServerName:機種名だけになってしまうので,Server(FriendlyName)+"|"+ServerName
----------------------------------------------------------------------------- */
function PostUpnpContents() {
	global $G_BROWSEMAX;

	$image="";
	if(isset($_POST['image'])) {
		$image=$_POST['image'];
		}
	print "Post UpnpContents Start Image[".$image."]\n";
	$url=$_POST['url'];
	$DirectoryUrl = getDeviceDescription($url);
	$cname="";
	if(isset($_POST['cname'])) {
		$cname=$_POST['cname'];
		}
//	$now = date("H:i:s");file_put_contents("./log.txt",$now." ". "url[$url]contentDirectoryUrl[$DirectoryUrl]\n", FILE_APPEND);
	if ($DirectoryUrl === FALSE) {
		print "Function:RequestUnLoadDisp();\n";
		die('Error: Unable to parse device description.');
		}
	$id="0";
	if (isset($_POST['id'])){
		 $id=$_POST['id'];
		 }
	$containers=PostUpnpContentsGet($DirectoryUrl,$id,$total);			// UPnP Contents Construction
//	$now = date("H:i:s");ob_start();print_r($containers);$buf=ob_get_contents();ob_end_clean();file_put_contents("./log.txt",$now." URL[".$DirectoryUrl."] id[".$id."] Array(".$total."):".$buf."\n", FILE_APPEND);
	if ((!is_array($containers))&&(strpos($containers,'Error:') === 0)){							// SOAP Requset Error
		print "Function:RequestUnLoadDisp();\n";
		if((stripos($cname,'DIGA') !== false)
			&&(stripos($containers,'rowseContentResponse(XMLBrowseContent)') !== false)&&(stripos($containers,'extract element') !== false)) {
			$containers="Please check your DIGA DMR Permission Settings.!";
			}
		die($containers);
		}
	if ($containers == ""){												// SOAP Requset NoData
		print "Post UpnpContents End No Response\n";
		return("");
		}
	if ($total > $G_BROWSEMAX) {										// Responseに件数が含まれない場合のブロック
		print "Function:RequestUnLoadDisp();RequestMsgBox('UPnP:WebUI UPnP Contains','Specified content exceeded (".$total."/Max:".$G_BROWSEMAX.")');\n";
		return("");
		}
	if ($total == 0) {
		print "Function:RequestUnLoadDisp();RequestMsgBox('UPnP:WebUI UPnP Contains','There is no content in the specified hierarchy');\n";
		return("");
		}

  ob_start();
	print "idDetaile:";					// Post時 Contents id
	print '<div id=idDetaile class="scrbar tabldt" style="top:55px;width:482px;">';
	print '<table class="sticky" border="3" cellpadding="0" bgcolor="#000000" frame=void style="table-layout:fixed;border-spacing:0;">';
	$imgwidth=40;
	print '<thead>';
	print '<tr style="width:400px;height:30px;background-color:#000000;">';					// 先頭は,戻るIcon
	print '<th rowspan="2">';
	$ptitle="";
	$pstr='\''.$url.'\'';
	if ((isset($_POST['bid']))&&($_POST['bid']!=="")){
		$pstr='\''.$url.'\',\''.$_POST['bid'].'\'';
		$ptitle="&nbsp;[".$_POST['bid']."]";
		}
	$post='EventUPnPDevices('.$pstr.');';
	print '<div id=idBack onclick="'.$post.'" style="width:'.$imgwidth.'px;cursor:pointer;">';
	print '<div align=center class="Parent-container" style="width:'.$imgwidth.'px;height:'.$imgwidth.'px;"><div class="Parent"></div>';
	$str="";							// Folder Iconの上にString
	print '<p class=imgstr style="width:'.$imgwidth.'px;height:'.$imgwidth.'px;">'.$str.'</p>';
	print '</div>';
	print '</div>';
	print '</th>';
	print '<th colspan="2">';			// Upper Line
	print '<div onclick="'.$post.'" class="line Parenline" style="width:390px;">Previous&nbsp;[Back to Parent]</div>';
	print '</th>';
	$cont_no=count(array_filter($containers, function ($item) { return isset($item['Mode']) && $item['Mode'] === "files"; }));		// Contens Count
	$cntstr=',\''.$cont_no.'\'';
	$postqueue='EventPlayUPnPReq(\'dir\',\'\',\''.$id.'\''.$cntstr.');';				// ID:Play
	print '<th>';
	print '<div onclick="'.$postqueue.'" class=Parentplay><div class="Parentpl-container"></div></div>';
	print '</th>';
	print '</tr>';
	print '<tr>';						// Under Line
	print '<th>';
	if($cont_no > 0) { $str=$cont_no; } else { $str=""; }
	print '<div class="uline thfix" style="width:220px;color:#66cdaa;"><span style="width:70px;display:inline-block;">Contents</span><span>'.$str.'</span></div>';
	print '</th>';
	print '<th>';
	print '<div class="uline thfix" style="width:160px;color:#66cdaa;">Go back to Previous Page'.$ptitle.'</div>';
	print '</th>';
	print '<th>';
	if($cont_no > 0) { $str="ALL"; } else { $str="&nbsp;&nbsp;-"; }
	print '<div class="uline thfix" style="width:30px;color:#66cdaa;text-align:center;">'.$str.'</div>';
	print '</th>';
	print '</tr>';
	print '</thead><tbody>';
	if (isset($_POST['id'])){
		 $bid=',\''.$_POST['id'].'\'';
		 }
	else {
		 $bid='';
		 }
	$count=PostUpnpContents_detaile($containers,$bid,$image,$cname);			// HTML Table detaile, Contents
	print '</tbody></table>';
	print '</div>';
	print "\n";
  $stdout=ob_get_contents();
  ob_end_clean();
  print $stdout;

	if (!isset($_POST['id'])){
		 print "Function:RequestUPnPContst('".$url."');\n";
		 $tmpid="0";
		 }
	else {
		 $tmpid=$_POST['id'];
		 }
	print "Function:document.getElementById('idSelUPnPURL').textContent='".$url."';\n";		// URLを設定する
	print "Function:document.getElementById('idSelPid').textContent='".$tmpid."';\n";		// ContentsIDを設定する
	print "Function:RequestUPnPResize();\n";												// ReSizeの高さを設定する
	print "Function:RequestUnLoadDisp();\n";
	if($count ==0) {									// 画像を表示しないケースで件数がない
		print "Function:RequestMsgBox('UPnP:WebUI UPnPRequest','There is no target file');\n";
		}
	print "Post UpnpContents End\n";

   GetEXTENDURLSet($id,$DirectoryUrl,$cname);				// 拡張Function有効化
	}

/*
	PostUpnpContents_detaile($containers,$bid,$image) : HTML Table detaile, Contents
	$containers: i  : Array:UpnpContents
	$bid       : i  : Used,Return Parent id
	$image     : i  : Rendererがimage対応かのI/F Rendererが選択されている場合 on/off Rendererが選択されていない場合""
	$cname     : i  : Contents Server Name
	@return    : o  : Count
	※ cname=Server+"|"+Name; ServerName:機種名だけになってしまうので,Server(FriendlyName)+"|"+ServerName
*/
function PostUpnpContents_detaile($containers,$bid,$image,$cname) {
	global $G_EXCLUDEXT;

	$name=explode("|",$cname);
	if((isset($name[1]))&&(stripos($name[1],"MinimServer") !== false)) { $minimserver="on"; } else { $minimserver=""; }

	$imgwidth=40;$count=0;
	foreach ($containers as $key => $ary) {
		$cont_no="";
		if ($minimserver=="on") {			// Minimserverの場合件数が取れないが 9999 albums| 9999 items| 9999 playlistsがあるのでこれを分解
			if ((!isset($ary['childCount']))||((isset($ary['childCount']))&&($ary['childCount']==""))) {			// child Contains数がない
			    if (preg_match('/(\d+)\s+(albums|items|playlists)/i', htmlspecialchars($ary['title']), $match)) {	// 古いタイプにはあり得るらしい
					if (isset($match[1])) { $ary['childCount']=$match[1]; }
					}
				}
			}
		if (isset($ary['childCount'])) {								// child Contains数
			$cont_no=$ary['childCount'];
			}
		if($bid !=='') {
			 $bidarg=$bid.',\''.$cont_no.'\'';
			 }
		else {
			 $bidarg=',\'\',\''.$cont_no.'\'';
			 }
		if($ary["Mode"]=="contns") {									// Contains
			 $post='EventUPnPDCont(\''.$ary['id'].'\''.$bidarg.');';
			 $part1="Contents";
			 $part2=$cont_no;
			 $part3="Sid[".$ary['id']."]";
			 $cntstr=',\''.$cont_no.'\'';
			 $postqueue='EventPlayUPnPReq(\'dir\',\'\',\''.$ary['id'].'\''.$cntstr.');';				// ID:Play
			 $ptype = isset($ary["protocolInfo"]) ? ArgMimeTypeParse($ary["protocolInfo"], "")[1] : null;
			 }
		else {															// Media(File)
			 $ext=substr($ary["url"], strrpos($ary["url"], '.') + 1);
			 $ext=strtolower($ext);
			 if ((in_array($ext,$G_EXCLUDEXT))&&($image=="off")) {				// Renderer Image"off"時、拡張子が画像のとき対象外
				continue;
				}
			 $post='EventPlayUPnPReq(\'file\',\''.$ary["url"].'\',\''.$ary['id'].'\');';	// Play
			 if ((strpos($ary["protocolInfo"],'audio') !== false)&&((isset($ary["artist"]))&& ($ary["artist"]!=="")) ) {
				  $part1=$ary["artist"];
				  }
			 else if ((strpos($ary["protocolInfo"],'application') !== false)&&((isset($ary["artist"]))&& ($ary["artist"]!=="")) ) {
				  $part1=$ary["artist"];			// upmpdcli:Qobuz
				  }
			 else {
				  $part1="MediaFile";
				  }
			 $year=ArgDateParse($ary['date']);
			 if ((strpos($ary["protocolInfo"],'audio') !== false)&&((isset($ary["genre"]))&& ($ary["genre"]!=="")) ) {
				  $part2='<span class=upnUPart1 style="width:116px;display:inline-block;">'.$year."&nbsp".$ary['album'].'</span><span>'.$ary["genre"].'</span>';
				  }
			 else {
				  $part2=$year."&nbsp".$ary['album'];
				  }
			 $str=parse_url($ary["url"]);
			 $part3=$str["path"];
			 $ptype=ArgMimeTypeParse($ary["protocolInfo"],"")[0];
			 }
		$count=$count+1;
		print '<tr>';
		print '<td rowspan="2">';
		print '<div onclick="'.$post.'" style="font-size:11px;width:'.$imgwidth.'px;cursor:pointer;">';
		$str="";						// Folder Iconの上にString
//		if (((isset($ary["ArtURI"]))&&($ary["ArtURI"]!==""))&&(ArgImageExists($ary["ArtURI"]))) {	// ImageFile Cheack遅くても良い場合に使用
		if ((isset($ary["ArtURI"]))&&($ary["ArtURI"]!=="")) {										// ImageFile Cheackしない場合に使用
			 $height=$imgwidth;
			 print '<img src=\''.$ary["ArtURI"].'\' width=\''.$imgwidth.'\' height=\''.$height.'\'><br>';
			 }
		else {
			 if($ary["Mode"]=="contns") {
				  print '<div align=center class="folder-container" style="width:'.$imgwidth.'px;height:'.$imgwidth.'px;"><div class="folder"></div>';
				  print '<p class=imgstr style="width:'.$imgwidth.'px;height:'.$imgwidth.'px;">'.$str.'</p>';
				  }
			 else if(strpos($ary["protocolInfo"],'audio') !== false){
				  print '<div align=center style="width:'.$imgwidth.'px;height:'.$imgwidth.'px;"><div class="music-icon"></div>';
				  }
			 else {
				  print '<div align=center style="width:'.$imgwidth.'px;height;'.$imgwidth.'px;"><div class="media-icon"></div>';
				  }
			 }
		print '</div>';
		print '</td>';
		print '<td colspan="2">';		// Upper Line
		print '<div onclick="'.$post.'" class=line style="width:390px;cursor:pointer;">'.htmlspecialchars($ary['title']).'</div>';
		print '</td>';
		if($ary["Mode"]=="contns") {
			 $postplay=$postqueue;			// ID:Play
			 }
		else {
			 $postplay=$post;				// Play
			 }
		print '<td>';
		print '<div onclick="'.$postplay.'" class=playline><div class="play-container"></div></div>';
		print '</td>';
		print '</tr>';
		print '<tr>';					// Under Line
		print '<td>';
		print '<div class=uline style="width:220px;"><span class=upnUPart1 style="width:70px;">'.$part1.'</span><span>'.$part2.'</span></div>';
		print '</td>';
		print '<td>';
		print '<div class=uline style="width:160px;">'.$part3.'</div>';
		print '</td>';
		print '<td>';
		print '<div class=uline style="width:30px;text-align:center;">'.$ptype.'</div>';
		print '</td>';
		print '</tr>';
		}
	return($count);
	}

/* -----------------------------------------------------------------------------
	GetEXTENDURLSet($id,$ssdpurl,$cname) ; Extend Function Set <<拡張Function>>
	$id      : i  ; ssdp id
	$ssdpurl : i  : ssdpUrl
	$cname   : i  : Contents Server Name
	※ cname=Server+"|"+Name; ServerName:機種名だけになってしまうので,Server(FriendlyName)+"|"+ServerName
----------------------------------------------------------------------------- */
function GetEXTENDURLSet($id,$ssdpurl,$cname) {
	if($id=="0") {								// Contents先頭でチェック
		$title="";
		$miniurl=GetEXTENDURL($ssdpurl,$cname,$title);
		if($miniurl !=="") {
			print "Function:ArgOptMiniDLNASet('".$miniurl."','".$title."');\n";	// Jsp有効化
			}
		}
	}

/*
	GetEXTENDURL($ssdpurl,$cname) ; Extend Function : MiniDLNA DBUpdateURL
	$ssdpurl : i  : ssdpUrl
	$cname   : i  : Contents Server Name
	$title   : io : Button Title
	@return  : o  : "":miniDLNAが発見できず , URL:miniDLNAのWebUi
*/
function GetEXTENDURL($ssdpurl,$cname,&$title) {
	global $G_EXFUNC;

	$urlscheme="http|https";				// 拡張Module:Scheme:Separate:|
	foreach ($G_EXFUNC as $key => $item) {
		$name=explode("|",$cname);
		if((isset($name[1]))&&(stripos($name[1],$key) === false)) {
			continue;
			}
		$scheme=explode("|",$urlscheme);	// 拡張Module:Scheme
		$port  =explode("|",$item["port"]);		// 拡張Module:Port
		$dir   =explode("|",$item["path"]);		// 拡張Module:Path
		$module=explode("|",$item["mod"]);		// 拡張Module:module
		$host  = parse_url($ssdpurl,PHP_URL_HOST);			// URLを組み立て
		foreach ($scheme as $sch) {
			foreach ($port as $p) {							// Portは設定必須
				foreach ($dir as $d) {
					$d=rtrim(trim($d."/"),"/");
					$por="";
					if($p !==""){
						$por=":".$p;
						}
					foreach ($module as $modu) {
						$mod=rtrim(trim($modu,"/"),"/");	// 拡張Module:File
						$url=$sch."://".$host.$por."/".$d."/".$mod;
						$headers = @get_headers($url);			// 存在チェック
						if ($headers && strpos($headers[0], '200') !== false) {
							$title=$item["btn"];
							return($url);
							}
						}
					}
				}
			}
		}
	return("");
	}

/* -----------------------------------------------------------------------------
	PostUpnpContentsGet($DirectoryUrl,$id,$total) : UPnP Contents Get
	$DirectoryUrl : i  : SOAP Request URL
	$id     : i  : ssdp id (Parent:"0")
	$total  : io : 有効数(Response<TotalMatches>Tagの値)
	@return : o  : Containers Array

	※Retry，Errorコードを行いSOAPRequestの動作
----------------------------------------------------------------------------- */
function PostUpnpContentsGet($DirectoryUrl,$id,&$total) {
	$containers=PostUpnpContents_Const($DirectoryUrl,$id,$total);
		if((!is_array($containers))&&(strpos($containers,'Error:') === 0)){						// SOAP Requset Error
		 }
	else {
		 $errstr=browseErrorParse($response);
		 if($errstr !=="") {
			 $containers='Error: '.$errstr;
			 $tmp = browseSOAPRequest($DirectoryUrl,"ClearError");	// ErrorClearしないと受け付けない機種がある
			 }
		 }
	return($containers);
	}

/*
	PostUpnpContents_Const($DirectoryUrl,$id,$total) : UPnP Contents Construction
	$DirectoryUrl : i  : SOAP Request URL
	$id     : i  : ssdp id (Parent:"0")
	$total  : io : 有効数(Response<TotalMatches>Tagの値)
	@return : o  : Containers Array
*/
function PostUpnpContents_Const($DirectoryUrl,$id,&$total) {
	$StartingIndex=0;$total=-1;			// DIGA対応:StartContents (DLNAの仕様範疇だが，他の機器は使用していない)
	$NumberReturne="";$TotalMatches="";	// $NumberReturne:GetContents ,$TotalMatches:TotalContents
	$containers=array();
	while(1) {							// <s:Body>のTotalMatchesまで取得
		$response = browseContent($DirectoryUrl,$id,(string)$StartingIndex);
		if (strpos($response,'Error:') === 0){			// SOAP Requset Error
			return($response);
			}
//		$now = date("H:i:s");file_put_contents("./log.txt",$now." getTransportState:".$response."\n", FILE_APPEND);
		preg_match('/<NumberReturned.*?>(.*?)<\/NumberReturned>/s', $response, $matches);	// preg_matchで<Result>タグの内容を抽出
		if (!empty($matches[1])) {							// DIGA対応:GetContents
			 $NumberReturne=$matches[1];
			 }
		preg_match('/<TotalMatches.*?>(.*?)<\/TotalMatches>/s', $response, $matches);		// preg_matchで<Result>タグの内容を抽出
		if (!empty($matches[1])) {							// DIGA対応:TotalContents
			$TotalMatches=$matches[1];
		 	}
		$container=browseContentArray($response);
		if((!is_array($container))&&(strpos($container,'Error:') === 0)){	// SOAP Requset Error
			return($container);
			}
		$contafile=browseContentParse($response);
		if((!is_array($contafile))&&(strpos($contafile,'Error:') === 0)){				// SOAP Requset Error
			return($contafile);
			}
		$container=browseContentMarge($container,$contafile);
		if ($NumberReturne=="") {							// Responseに$NumberReturneがなければそのまま
			$containers=$container;
			break;
			}
		$containers=ArgNKeyArrayMarge($containers,$container);		// 複数ArrayMarge:NumberKey,renumber
		$StartingIndex=$StartingIndex+(int)$NumberReturne;	// StartIndex+Get数
		if ($StartingIndex >=(int)$TotalMatches){
			break;
			}
		}
	if ($TotalMatches !=="") {
		$total=intval($TotalMatches);
		}
	return($containers);
	}

/*
	ArgImageExists($url) : Image File Chaeck 
	$url    : i  : IImage File URL
	@return : o  : True:Exists False: No Exists
*/
function ArgImageExists($url) {
	$fp = @fopen($url, 'rb');	//'rb' ReadOnly, Binary
	if ($fp) {
		fclose($fp);
		return true;
		}
	return false;
	}

/* -----------------------------------------------------------------------------
	PostUPnPRequest() : UPnP Request , Play
	$_POST['url']:ControlURL                 ,$_POST['pid']:parentID ,
	$_POST['mediaurl']:再生対象MediaFIle URL ,$_POST['id']:MediaId ,
	$_POST['conturl']:Renderer URL           ,$_POST['modename']:Renderer ModeName
	※ modeName=Render+"|"+modeName; modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
	SoftRenderは，設定でFriendlyNameを決められるが，H/WRenderは機種固定,詳細はmodeNameと考える。
----------------------------------------------------------------------------- */
function PostUPnPRequest() {
	print "Post UPnPRequest Start\n";
	$url        = $_POST['url']       ?? '';
	$parentID   = $_POST['pid']       ?? '';
	$mediaurl   = $_POST['mediaurl']  ?? '';
	$controlUrl = $_POST['conturl']   ?? '';
	$id         = $_POST['id']        ?? '';
	$modename   = $_POST['modename']  ?? '';

// $fp=fopen("log/mpdupnp.log","a");fwrite($fp,"PostUPnPRequest Start URL[".$url."]\n");fclose($fp);
	print "URL[".$url."]ParentID[".$parentID."]CntURL[".$controlUrl."]mediaurl[".$mediaurl."]id[".$id."]\n";
	PostUPnPRequestPlay($controlUrl,$url,$parentID,$mediaurl,$id,$modename);
// $fp=fopen("log/mpdupnp.log","a");fwrite($fp,"PostUPnPRequest End\n");fclose($fp);
	print "Post UPnPRequest End\n";
    flush();
    ob_flush();
	}

/* -----------------------------------------------------------------------------
	PostUPnPQueueReq() : UPnP Request id (parentID以下を再生)
	@return : o  : PlaylistCount(件数)

	$_POST['url']       : SSDP URL (再生対象)
	$_POST['id']        : id(parentID)以下を再生 , $_POST['playid']  : 最初にPlayするid(未指定時は最初のid)
	$_POST['conturl']   : Renderer URL           , $_POST['modename']:Renderer ModeName
	$_POST['image']     : Rendererがimage対応かのI/F Rendererが選択されている場合 on/off Rendererが選択されていない場合""
	$_POST['serverplay']: "on":ServerPlayMode
	                      Play関係は,Rendererに直接指示。ServerProcessはRendererからのStatusで状態の変化を知る。
	                      Stopは,NextPlayのタイミングにもなるので、Process間通信で"Kill"を送出
	                      ※後にServerProcessは、単体Sourceになる可能性もあるので纏めるためExtendとしている。
	※ modeName=Render+"|"+modeName; modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
----------------------------------------------------------------------------- */
function PostUPnPQueueReq() {
	global $G_BROWSEMAX,$G_PLAYMAX,$G_EXCLUDEXT,$G_SYSVMSG;

	$image="";
	if (isset($_POST['image'])){
		$image=$_POST['image'];
		}
	$serverplay="";$serverplay_str="";$digits="0";$post_playid="";
	if(isset($_POST['serverplay'])) {		// ClientでCheack
		$serverplay=$_POST['serverplay'];
		}

	if(isset($_POST['playid'])) {			// 先頭のPlayidを指定
		$post_playid=$_POST['playid'];
		}

	if(isset($_POST['digits'])) {			// Client識別
		$digits=$_POST['digits'];
		}

	if($serverplay=="on") { $serverplay_str="ServerPlay "; }
	print "Post UPnP Queue Request Start ".$serverplay_str."image[".$image."]\n";
	$controlUrl=$_POST['conturl'];
	$url=$_POST['url'];
	$modename=$_POST['modename'];
	if (isset($_POST['id'])){
		 $parentID=$_POST['id'];
		 }
	else {
		 $parentID="0";
		 }
	$DirectoryUrl = getDeviceDescription($url);
	if ($DirectoryUrl === FALSE) {
		die('Error: Unable to parse device description.');
		}
	$containers=PostUpnpContentsGet($DirectoryUrl,$parentID,$total);	// UPnP Contents Construction
	if ((!is_array($containers))&&(strpos($containers,'Error:') === 0)){			// SOAP Requset Error
		die($containers);
		}
	if ($total > $G_BROWSEMAX) {
		print "Function:RequestUnLoadDisp();RequestMsgBox('UPnP:WebUI UPnP Contains','Specified content exceeded (".$total."/Max:".$G_BROWSEMAX.")');\n";
		return("");
		}
	if ($total == 0) {
		print "Function:RequestUnLoadDisp();RequestMsgBox('UPnP:WebUI UPnP Contains','There is no content in the specified hierarchy');\n";
		return("");
		}
	$result=0;$play=-1;$playurl="";$playid="";$idxhead=-1;
	foreach ($containers as $key => $ary) {
		if($ary["Mode"]=="contns") { continue; }			// Contains
		if($ary["Mode"] !=="contns") {
			$ext=substr($ary["url"], strrpos($ary["url"], '.') + 1);
			$ext=strtolower($ext);
			if ((in_array($ext,$G_EXCLUDEXT))&&($image=="off")) { continue; }	// Renderer Image"off"時、拡張子が画像のとき対象外
			}
		if ($post_playid !=="") {							// Playidが指定されたときMusicSearch
			if($ary["id"]==$post_playid) {
				$playurl=$ary["url"];
				$playid=$ary["id"];
				$play=$result;
				}
			}
		else{
			if($play == -1) {									// 最初にPlayするURL
				 $playurl=$ary["url"];
				 $playid=$ary["id"];
				 $play=$result;
				 }
			}
		$result=$result+1;
		if($idxhead == -1) { $idxhead=$key; }					// ContentsのTopをkeep
		}
	if($result > $G_PLAYMAX) {
		print "Function:RequestMsgBox('UPnP:WebUI UPnP Request','Specified Request exceeded (".$result."/Max:".$G_PLAYMAX.")');\n";
		return("");
		}
	if($result == 0) {
		print "Function:RequestMsgBox('UPnP:WebUI UPnP Request','There is no content in the specified hierarchy');\n";
		return("");
		}
	if($result > 0) {										// Playbox Headerを生成する
		$contns=$containers[$idxhead];
		_PostUPnPBoxHeader($contns,"PLAYING","1");			// JSP EventLoopの初期値として"PLAYING"をセットする。
		}													// "1": Jsp上でEventLoopの配列先頭(-1)で使用
	if ((($result >=1)&&($serverplay !=="on"))||(($result ==1)&&($serverplay =="on"))) {		// ServerPlay:offは先頭をPlay,ServerPlay:onでも1曲の場合Play(1曲だけなのでResource節約)
		PostUPnPRequestPlay($controlUrl,$url,$parentID,$playurl,$playid,$modename);				// 先頭をPlay
		}
	if($result > 1) {				// 複数件数のときPlaylist
		_PostUPnP_Queue_detaile($url,$parentID,$containers,$image);
		print "Function:RequestUPnPResize();\n";												// ReSizeの高さを設定する
		if ($serverplay=="on") {					// 簡易的にServerで連続Play
			$type=0;
			if($G_SYSVMSG !=="") {					// $G_SYSVMSG : Used:System V Message Queue
				$fpftok=ArgFtokOpen($controlUrl);	// Prosess間通信(非同期)を使用してKillを行う:QueueKey:RendererのcontrolUrl
				ArgFtokRemove($fpftok);				// Queueを捨てる
				$type= rand(1,1000);				// TypeはRandom
				$send="Kill:".$digits;
				$result=ArgFtokSend($fpftok,$send,$type);	// Prosess間通信Send(Ftok Qurue,in)
				if(is_null($result["message"])) {
					print "Function:RequestMsgBox('UPnP:WebUI UPnP ServerPlay','ArgFtokSend Send Queue False,Continue Key[".$fpftok."]URL[".$controlUrl."]');\n";
					}
				}
//			print "Function:alert('Post Send[".$type."]digits[".$digits."]playid[".$playid."]sned[".$send."]');\n";
			print "Function:RequestByPost(POSTMOD,'p=serverplay&url=".htmlspecialchars($url)."&id=".htmlspecialchars($parentID)."&playid=".htmlspecialchars($post_playid)."&type=".$type."&digits=".$digits."&conturl=".htmlspecialchars($controlUrl)."&image=".$image."&modename=".htmlspecialchars($modename)."');\n";
//			$now = date("H:i:s");file_put_contents("./log.txt",$now." ". "url[$url]id[$parentID] conturl[$controlUrl]modename[$modename]\n", FILE_APPEND);
			}
		}
	else{
		print "idPlaylist:";					// 空のPlaylist
		print '<div id=idPlaylist class="scrbar tabldt" style="top:236px;width:482px;background-color:#000000;">';
		print '</div>';
		print "\n";
		}
	print "Post UPnP Queue Request End\n";
	flush();
	ob_flush();
	return($result);
	}

/*
	_PostUPnPBoxHeader($containers,$state,$postion) : Display PlayBoxHeader
	$containers : i  : PlayBoxHeaderのContents Construction
	$state      : i  : Status : PlayBoxに表示するStats PLAYING....
	$postion    : i  : Postion: PlayBoxに表示する PlayPostion 先頭:1 NULL時"PlayPostion"を表示しない
*/
function _PostUPnPBoxHeader($containers,$state,$postion=NULL) {
	global $G_EXCLUDEXT,$G_IMGDURATION;

	print "idPlaydtl:";					// Post時 Contents id
	print '<div id=idPlaydtl style="position:absolute;top:30px;height:500px;color:#ffffff;">';
	$imgwidth="150";$height="150";
//	if ((isset($containers['ArtURI']))&&($containers['ArtURI']!=="")) {		// ImageFile Cheackしない場合に使用
	if (((isset($containers["ArtURI"]))&&($containers["ArtURI"]!==""))&&(ArgImageExists($containers["ArtURI"]))) {
		 $cssimg="block";$cssdiv="none";$src=$containers["ArtURI"];
		 }
	else {
		 $cssimg="none";$cssdiv="block";$src="";
		 }
	print '<img src=\''.$src.'\' width=\''.$imgwidth.'\' height=\''.$height.'\' style="display:'.$cssimg.';">';
	print '<div align=center class="playboxdiv" style="width:'.$imgwidth.'px;height:'.$imgwidth.'px;display:'.$cssdiv.';"><div class="mediaplay"></div></div>';

	$ext=substr($containers["url"], strrpos($containers["url"], '.') + 1);
	$ext=strtolower($ext);
	$type_img="0";
	if (in_array($ext,$G_EXCLUDEXT)) {				// Image on
		$type_img="1";
		}

	$str="";							// Edit Artist
	if ((isset($containers['TrackURI']))&&($containers['TrackURI']!=="")) {
		$str="No Artist";
		if((isset($containers['artist']))&&($containers['artist']!=="")) {
			 $str=$containers['artist'];
			 }
		else if((isset($containers['creator']))&&($containers['creator']!=="")) {
			 $str=$containers['creator'];
			 }
		}
	print '<div class=nowplayhd style="position:absolute;top:0px;left:150px;">'.$str.'</div>';
	$str="";						// Edit Album
	if((isset($containers['album']))&&($containers['album']!=="")) {
		 $str=$containers['album'];
		 }
	print '<div class=nowplayhd style="position:absolute;top:30px;left:150px;">'.$str.'</div>';
	$str="";						// Edit Title
	if ((isset($containers['TrackURI']))&&($containers['TrackURI']!=="")) {
		$str="No Title";
		if ((isset($containers['title']))&&($containers['title']!=="")) {
			 $str=htmlspecialchars($containers['title']);
			 }
		}
	print '<div class=nowplayhd style="position:absolute;top:62px;left:150px;">'.$str.'</div>';
	$str="";						// Edit Track
	if ((isset($containers['originalTrackNumber']))&&($containers['originalTrackNumber']!=="")) {
		 $str="Track:".$containers['originalTrackNumber'];
		 }
	print '<div class=nowplayhds style="position:absolute;top:94px;left:150px;">'.$str.'</div>';
	$str="";						// Edit Time Count
	if ((isset($containers['TrackURI']))&&($containers['TrackURI']!=="")) {
		if ((isset($containers['RelTime']))&&($containers['RelTime']!=="")) {
			 $str=$containers['RelTime'];
			 }
		}
	$str = preg_replace('/^00:/', '0:', $str);
	print '<div id=idTime class=nowplayhds style="position:absolute;top:94px;left:220px;">'.$str.'</div>';
	$str="";						// Edit Music Time
	if ((isset($containers['duration']))&&($containers['duration']!=="")) {
		 if ((isset($containers['duration']))&&($containers['duration']!=="")) {
			 $str=$containers['duration'];
			 $str=explode(".",$str)[0];
			 }
		 }
	else if ((isset($containers['TrackURI']))&&($containers['TrackURI']!=="")) {		// RendererからのMusicTime(Metaではない)保険を掛けておく
			 if ((isset($containers['TrackDuration']))&&($containers['TrackDuration']!=="")) {
			 $str=$containers['TrackDuration'];
			 }
		 }
	if(($str=="")&&($type_img=="1")) {
		$str=$G_IMGDURATION;										// Play,Image Duration
		}
	$str = preg_replace('/^00:/', '0:', $str);
	print '<div id=idDuration class=nowplayhds style="position:absolute;top:94px;left:280px;">'.$str.'</div>';
	$str="";						// Edit Play Position
	if (!is_null($postion)){
		$str="Play Position : ".$postion;
		}
	print '<div id=idPlaypos class=nowplayhds style="position:absolute;top:94px;left:370px;">'.$str.'</div>';
	print '<div id=idStatus class=nowplayhds style="position:absolute;top:114px;left:150px;">'.$state.'</div>';

	print '<div id=idsmode class=nowplayhds style="position:absolute;top:114px;left:396px;color:#ffff00;"></div>';

	print '<div id=idImage class=nowplayhds style="position:absolute;top:114px;left:300px;visibility:hidden;">'.$type_img.'</div>';
	if((isset($containers['TrackURI']))&&($containers['TrackURI']!=="")) {
		print '<div id=idNowURL class=nowplayhds style="position:absolute;top:128px;left:150px;font-size:9px;height:16px;">'.$containers['TrackURI'].'</div>';
		}
	print '</div>';
	print "\n";

	print "Function:EventPlayServerDisp();\n";
//	print "Function:alert('".$containers['title']."');;\n";
//	flush();ob_flush();はCall元で行う
	}

/*
	_PostUPnP_Queue_detaile($url,$parentID,$containers) : Select Containers Detaile Display
	$url       : i  : SSDP URL (再生対象) ,  $_POST['id']:id(parentID)以下を再生 ,
	$parentID  : i  : id(parentID)以下を再生
	$containers: i  : PlayBoxHeaderのContents Construction
*/
function _PostUPnP_Queue_detaile($url,$parentID,$containers,$image) {
	global $G_EXCLUDEXT;

	$imgwidth=40;
	print "\n";
	print "idPlaylist:";					// Post時 Contents id
	print '<div id=idPlaylist class="scrbar tabldt" style="top:236px;width:482px;background-color:#000000;">';
	print '<table id=idPlaylistTB class="sticky" border="3" cellpadding="0" bgcolor="#000000" frame=void style="table-layout:fixed;border-spacing:0;">';
	$no=0;
	foreach ($containers as $key => $ary) {
		if($ary["Mode"]=="contns") {									// Contains
			continue;
			}
		$type_img="0";
		if($ary["Mode"] !=="contns") {
			$ext=substr($ary["url"], strrpos($ary["url"], '.') + 1);
			$ext=strtolower($ext);
			if ((in_array($ext,$G_EXCLUDEXT))&&($image=="off")) {				// Renderer Image"off"時、拡張子が画像のとき対象外
				continue;
				}
			if (in_array($ext,$G_EXCLUDEXT)) {				// Image on
				$type_img="1";
				}
			}
		$no=$no+1;
		$post='EventPlayPLReq('.$no.',\''.$url.'\',\''.$parentID.'\',\''.$ary["url"].'\',\''.$ary['id'].'\');';	// Play
		if ((strpos($ary["protocolInfo"],'audio') !== false)&&((isset($ary["artist"]))&& ($ary["artist"]!=="")) ) {
			 $part1=$ary["artist"];
			 }
		else if ((strpos($ary["protocolInfo"],'application') !== false)&&((isset($ary["artist"]))&& ($ary["artist"]!=="")) ) {
			 $part1=$ary["artist"];							// upmpdcli:Qobuz
			 }
		else {
			 $part1="MediaFile";
			 }
		$year=ArgDateParse($ary["date"]);
		if ((strpos($ary["protocolInfo"],'audio') !== false)&&((isset($ary["genre"]))&& ($ary["genre"]!=="")) ) {
			 $part2='<span class=upnUPart1 style="width:100px;">'.$year."&nbsp".$ary['album'].'</span><span>'.$ary["genre"].'</span>';
			 }
		else {
			 $part2=$year."&nbsp".$ary['album'];
			 }
		$str=parse_url($ary["url"]);
		$part3=$str["path"];
		$ptype=ArgMimeTypeParse($ary["protocolInfo"],"")[0];
		print '<tr>';
		print '<td rowspan="2"><div class=playline style="width:30px;font-size:10px">';
		print $no;
		print '</div></td>';
		print '<td rowspan="2">';
		print '<div onclick="'.$post.'" style="font-size:11px;width:'.$imgwidth.'px;cursor:pointer;">';
		$str="";						// Folder Iconの上にString
//		if (((isset($ary["ArtURI"]))&&($ary["ArtURI"]!==""))&&(ArgImageExists($ary["ArtURI"]))) {	// ImageFile Cheack遅くても良い場合に使用
		if ((isset($ary["ArtURI"]))&&($ary["ArtURI"]!=="")) {										// ImageFile Cheackしない場合に使用
			 $height=$imgwidth;
			 print '<img src=\''.$ary["ArtURI"].'\' width=\''.$imgwidth.'\' height=\''.$height.'\'><br>';
			 }
		else if(strpos($ary["protocolInfo"],'audio') !== false){
			 print '<div align=center style="width:'.$imgwidth.'px;height:'.$imgwidth.'px;"><div class="music-icon"></div>';
			 }
		else {
			 print '<div align=center style="width:'.$imgwidth.'px;height;'.$imgwidth.'px;"><div class="media-icon"></div>';
			 }
		print '</div>';
		print '</td>';
		print '<td colspan="2">';		// Upper Line
		print '<div onclick="'.$post.'" class=line style="width:359px;cursor:pointer;">'.htmlspecialchars($ary['title']).'</div>';
		print '</td>';
		print '<td>';
		print '<div onclick="'.$post.'" class=playline><div class="play-container"></div></div>';
		print '<div style="visibility:hidden;width:0px;height:0px;">'.($ary['originalTrackNumber'] ?? '').'</div>';
		print '</td>';
		print '</tr>';
		print '<tr>';					// Under Line
		print '<td>';
		print '<div class=uline style="width:201px;"><span class=upnUPart1 style="width:70px;">'.$part1.'</span><span>'.$part2.'</span></div>';
		print '</td>';
		print '<td>';
		print '<div class=uline style="width:144px;">'.$part3.'</div>';
		print '</td>';
		print '<td>';
		print '<div class=uline style="width:29px;text-align:center;">'.$ptype.'</div>';
		$str="";						// Edit Music Time : hidden
		if ((isset($ary['duration']))&&($ary['duration']!=="")) {
			if ((isset($ary['duration']))&&($ary['duration']!=="")) {
				$str=$ary['duration'];
				$str=explode(".",$str)[0];
		 		}
			 }
		print '<div style="visibility:hidden;width:0px;height:0px;">'.$str.'</div>';
		if($type_img =="1") {
			print '<div style="visibility:hidden;width:0px;height:0px;">'.$type_img.'</div>';			// Image:"1"
			}
		print '</td>';
		print '</tr>';
		}
	print '</table>';
	print '<div id=idPlaylistTtl class=playfot>Playlist <span style="color:#ffffff;">TotalFiles:'.$no.'</span></div>';
	print '</div>';
	print "\n";
//	flush();ob_flush();はCall元で行う
	}

/*
	PostUPnPRequestPlay($controlUrl,$url,$parentID,$mediaurl,$id,$modename) : Renderer Play Request
	$controlUrl: i  : AVTransportのURL
	$url       : i  : SSDP URL (再生対象)
	$parentID  : i  : parentID(idの親)
	$mediaurl  : i  : PlayURL(idのURL)
	$id        : i  : Play id
	$modename  : i  : Render ModeName
	※ modeName=Render+"|"+modeName; modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
 */
function PostUPnPRequestPlay($controlUrl,$url,$parentID,$mediaurl,$id,$modename) {
	$stopParams = array(		// 一部の機器ではStop送ってから行うための対応
		"InstanceID" => 0,
		);
	sendUpnpRequest($controlUrl, "AVTransport", "Stop", $stopParams,$modename);

	$MetaData = createCurrentURIMetaData($url,$parentID,$id);	// $MetaData: Error時:""再生は続行できる
	$params = array(
		"InstanceID" => "0",
		"CurrentURI" => $mediaurl,
		"CurrentURIMetaData" => $MetaData,
		);
	$response = sendUpnpRequest($controlUrl,"AVTransport","SetAVTransportURI",$params,$modename);
	if ($response === false) { print "Post UPnPRequest Play Timeout\n"; }
	else if ($response === null) { print "Error: UPnPRequest Play false\n"; }
	else { print "Post UPnPRequest Play Success\n"; }

	$playParams = array( 		 // 再生を開始
		"InstanceID" => "0",
		"Speed" => "1",
		);
	$response =sendUpnpRequest($controlUrl, "AVTransport", "Play", $playParams,$modename);
	if ($response === false) { print "Post UPnPRequest Play Timeout\n"; }
	else if ($response === null) { print "Error: UPnPRequest Play false\n"; }
	else { print "Post UPnPRequest Play Success\n"; }
	flush();
	ob_flush();
	}

/* -----------------------------------------------------------------------------
	PostPlayPanel() : PlayPanel Command
	$_POST['cmd']     :cmd play|stop , $_POST['conturl']:Renderer URL , $_POST['modename']:Renderer ModeName ,
	$_POST['fasttime']:FFW/REWで使用 , $_POST['url']    :PlayMediaURL 
----------------------------------------------------------------------------- */
function PostPlayPanel() {
	print "Post UPnP Renderer PlayPanel Start\n";
	$modename="";$cmd="";$url="";$conturl="";$fasttime="";$serverplay="";

	if (isset($_POST['cmd'])) {
		$cmd=$_POST['cmd'];
		}
	if (isset($_POST['conturl'])) {
		$controlUrl=$_POST['conturl'];
		}
	if (isset($_POST['modename'])) {
		$modename=$_POST['modename'];
		}
	if (isset($_POST['fasttime'])) {	// FFW REWで使用(FastTime"hh:mm:ss")
		$fasttime=$_POST['fasttime'];
		}
	if (isset($_POST['url'])) {			// 現在urlは使わない方針
		$url=$_POST['url'];
		}
	if (isset($_POST['serverplay'])) {		// 現在urlは使わない方針
		$serverplay=$_POST['serverplay'];
		}
/* ---- Command Process -------------------------------------- */
	if ($cmd=="Play") {					// Command:play
		 $playParams = array(				// 再生を開始
			"InstanceID" => "0",
			"Speed" => "1",
			);
		 sendUpnpRequest($controlUrl, "AVTransport", "Play", $playParams,$modename);
		 }
	else if (($cmd=="Stop")||($cmd=="Pause")) {				// Command:stop
		 $stopParams = array(				// Stop送信
			"InstanceID" => 0,
			);
		 sendUpnpRequest($controlUrl, "AVTransport", $cmd , $stopParams,$modename);
		 PostPlayPanel_StopExt($controlUrl,$serverplay);	// Playbox Command, Stop Extend
		 }
	else if ($cmd=="Clear") {			// Command:stop
		 $params = array(
			"InstanceID" => "0",
			"CurrentURI" => "",
			"CurrentURIMetaData" => "",
			);
		 $response = sendUpnpRequest($controlUrl,"AVTransport","SetAVTransportURI",$params,$modename);
		 }
	else if ($cmd=="ErrorClear") {			// Command:stop
		 $result=browseSOAPRequest($controlUrl,"ClearError");	// ErrorClearしないと受け付けない機種がある
		 }
	else if ($cmd=="FFW") {			// Command:seek
		 $playParams = array(		
			"InstanceID" => "0",
			"Unit" => "REL_TIME",
			"Target" => $fasttime,	// 指定Time
			);
		 sendUpnpRequest($controlUrl, "AVTransport", "Seek", $playParams,$modename);
		 }
	else if ($cmd=="REW") {			// Command:seek
		 $playParams = array(
			"InstanceID" => "0",
			"Unit" => "REL_TIME",
			"Target" => $fasttime,	// 指定Time
			);
		 sendUpnpRequest($controlUrl, "AVTransport", "Seek", $playParams,$modename);
		 }
	print "Post UPnP Renderer PlayPanel (".$cmd.") End\n";
	flush();
	ob_flush();
	}

/*
	PostPlayPanel_StopExt($conturl,$serverplay) : Playbox Command, Stop Extend
	$controlUrl : i  : Renderer ControlURL
	$serverplay : i  : ServerPlay:"on" , eles ""
*/
function PostPlayPanel_StopExt($controlUrl,$serverplay) {
	global $G_SYSVMSG;

	if($G_SYSVMSG !=="") {								// $G_SYSVMSG : Used:System V Message Queue
		if($serverplay=="on") {
			$fpftok=ArgFtokOpen($controlUrl);			// Prosess間通信(非同期)を使用してKillを行う:QueueKey:RendererのcontrolUrl
			ArgFtokRemove($fpftok);						// Queueを捨てる
			$type= rand(1,1000);						// TypeはRandom
			$result=ArgFtokSend($fpftok,"Kill",$type);	// Prosess間通信Send(Ftok Qurue,in)
			if(is_null($result["message"])) {
				print "Function:RequestMsgBox('UPnP:WebUI UPnP ServerPlay','ArgFtokSend Send Queue False,Continue Key[".$fpftok."]URL[".$controlUrl."]');\n";
				}
//			print "Function:alert('Post Cmd Stop[".$type."]');\n";
			return("on");
			}
		}
	return("");
	}

/* -----------------------------------------------------------------------------
	PostNowPlayRequest() : Now Play Display
	$_POST['conturl']:Renderer URL , $_POST['modename']:Renderer ModeName
	※ modeName=Render+"|"+modeName; modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
----------------------------------------------------------------------------- */
function PostNowPlayRequest() {
	print "Post UPnP NowPlay Request Start\n";
	$controlUrl=$_POST['conturl'];
	$modename="";
	if(isset($_POST['modename'])) {
		$modename=$_POST['modename'];
		}
//$fp=fopen("log/mpdupnp.log","a");fwrite($fp,"getTransportInfo:controlUrl(".$controlUrl.",".$modename.")\n");fclose($fp);
	$containers = getTransportInfo($controlUrl,$modename);					// 再生中のContains
//$fp=fopen("log/mpdupnp.log","a");fwrite($fp,"getTransportInfo:".(print_r($containers, true))."\n");fclose($fp);
	if (!is_array($containers)) {
		print "Post UPnP NowPlay Request NowPlay,FailureEnd\n";
		flush();
		ob_flush();
		return;
		}
	$state = getTransportState($controlUrl,$modename);
	_PostUPnPBoxHeader($containers,$state);					// Playbox Headerを生成する
	print "Post UPnP NowPlay Request End\n";
	flush();
	ob_flush();
	}

/* ****************************************************************************
   ServerProcess
  -----------------------------------------------------------------------------
	PostQueueNonStop() : 簡易的にServerで連続Play : UPnP Request id (parentID以下を再生)
	PostUPnPQueueReq()と同じ引数+'type':MessageType
	$_POST['url']    : SSDP URL (再生対象)  
	$_POST['id']     :id(parentID)以下を再生 , $_POST['playid']   : 最初にPlayするid(未指定時は最初のid)
	$_POST['conturl']: Renderer URL           , $_POST['modename']: Renderer ModeName
	$_POST['image']  : Rendererがimage対応かのI/F Rendererが選択されている場合 on/off Rendererが選択されていない場合""
	$_POST['type']   : MessageType(Process間通信Key:自身のKey)
	$_POST['digits'] : Client識別(同じBrowserのTabまでの識別)
	QueueKey:RendererのcontrolUrlを使用する。typeはProsessごとにRandomに取得。
	Post起動するときtypeを指定する。他のPorosessはQueueをGetし終了(自身は、typeと一致Queueは捨てる)
	※ 起動は,PostUPnPQueueReq()でServerPlayのとき行う。Process間通信はRendererごとにQueue。
	   "type":はRandomに取得する。"Function:{ajax}"で起動する。
	※ 停止でProcessを停止するため、停止後、連続Playする場合、'playid'を使用して最初にPlayするidを指定する。途中からPlayを行う
	※ modeName=Render+"|"+modeName; modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
	※ 後に移植が面倒でないように1関数で作成
	※ Function ftokでAP間Queue更新ができるので後に排他はこれで作成する(ArgFtokOpen/Send/Receive)
***************************************************************************** */
function PostQueueNonStop() {
	global $G_BROWSEMAX,$G_EXCLUDEXT,$G_IMGDURATION,$G_TRANSOF,$G_COMSEC,$G_SYSVMSG;

	print "QueueNonStop Startr\n";
	$image="";
	if (isset($_POST['image'])){
		$image=$_POST['image'];
		}
	print "Post UPnP NonStop Start\n";
	$controlUrl=$_POST['conturl'];
	$url=$_POST['url'];
	$DirectoryUrl = getDeviceDescription($url);
	if ($deviceDescription === FALSE) {
		return;
		}
	$modename=$_POST['modename'];
	if (isset($_POST['id'])){
		 $parentID=$_POST['id'];
		 }
	else {
		 $parentID="0";
		 }

	$post_playid="";
	if(isset($_POST['playid'])) {			// 先頭のPlayidを指定
		$post_playid=$_POST['playid'];
		}

	if (isset($_POST['type'])){				// MessageType(Process間通信Key)
		 $Type=$_POST['type'];
		 }
	else {
		 $Type=0;
		 }
	if(isset($_POST['digits'])) {			// Client識別
		$digits=$_POST['digits'];
		}
	$SVR_SLEEP=10;					// SleepTimer(JSPと同じ値にする必要はない異なる方がRendererに重ならない)
	$SVR_COMSEC=$G_COMSEC;			// Status TimeUp Status="STOPPED"時のマージンこの値を誤差として継続再生と見みなす
	$SVR_STOPREVISE=$SVR_SLEEP+10;	// Status TimeUp Status="STOPPED"時 TimeCountが遅れている誤差。FFWは"PLAYING"だがFFWで偶々/FFWで停止になるケースとSleepの誤差 : ServerPlayのみ
	$SVR_PAUSE =40;					// Status TimeUp Status="PAUSED_PLAYBACK":設定Secを超えると停止(Resourceを消費しない様に) : ServerPlayのみ
	$SVR_TRANS=$G_TRANSOF;			// Status TimeUp Status="TRANSITIONING":設定Secを超えると停止(巨大FileかNet異常)。
	$SVR_TRYCOUNT=10;				// Renderer Receive RetryCount : ServerPlayのみ
	$SVR_SLEEP_TRANS=2;				// Renderer:Image TRANSITIONING Sleep(Sec)
	$SVR_LOSTSEC=$SVR_SLEEP*3;		// 予想PlayTimeとぼ誤差(別PCで同じ曲を選択したとみなす:気休めにしかならない)
	 								// $SVR_SLEEPの誤差は必ずでるので3倍に設定
	$fpftok=ArgFtokOpen($controlUrl);	// Prosess間通信(非同期)を使用してKillを行う
//--------- 1St Music 
	$containers=PostUpnpContents_Const($DirectoryUrl,$parentID,$total);	// UPnP Contents Construction
	$playurl="";$playid="";
	foreach ($containers as $key => $ary) {
		if($ary["Mode"]=="contns") { continue; }			// Contains
		if($ary["Mode"] !=="contns") {
			$ext=substr($ary["url"], strrpos($ary["url"], '.') + 1);
			$ext=strtolower($ext);
			if ((in_array($ext,$G_EXCLUDEXT))&&($image=="off"))  { continue; }		// Renderer Image"off"時、拡張子が画像のとき対象外
			}
		if ($post_playid !=="") {		// Playidが指定されたときMusicSearch
			if($ary["id"]==$post_playid) {
				$playurl=$ary["url"];
				$playid=$ary["id"];
				break;
				}
			}
		else{
			if ($playid=="") {
				$playurl=$ary["url"];		// 先頭のMusic
				$playid=$ary["id"];
				break;
				}
			}
		}
//--------- Containers Loop
	$endmode=""; 					// 強制終了 : "{String}":MsgboxString , "{String}#{cmd}":#1:cmdを実行
	$imageifg="";$musictime=0;		// 監視タイマ
	if($playurl !=="") {
		while (true) {				// 再生Control
			$ext=substr($playurl, strrpos($playurl, '.') + 1);
			$ext=strtolower($ext);
			if ((in_array($ext,$G_EXCLUDEXT))&&($image=="off"))  { continue; }				// Renderer Image"off"時、拡張子が画像のとき対象外
			if  (in_array($ext,$G_EXCLUDEXT)) { $imageifg="on"; } else { $imageifg=""; }	// Image:on/off
			PostUPnPRequestPlay($controlUrl,$url,$parentID,$playurl,$playid,$modename);
//			$fp=fopen("log/mpdupnp.log","a");$now=date("Y/m/d H:i:s");fwrite($fp,$now." PostUPnPRequestPlay URL(".$playurl.") imageifg(".$imageifg.")\n");fclose($fp);
			$musictime=0;$stat_pause=0;$stat_stoped=0;$stat_trans=0;$endmode="";$musicmeta=0;
			$nowplayurl=$playurl;
//--------- Containers終了を監視
			$trycount=0;
	 	    while (true) {			// 再生状態を監視
				if($G_SYSVMSG !=="") {										// $G_SYSVMSG : Used:System V Message Queue
					$result_rcv=ArgFtokReceive($fpftok,$Type);				// Prosess間通信(非同期)を使用
					if(!is_null($result_rcv["message"])) {
						$parts = explode('Kill:',$result_rcv["message"]);
						$rcv_digits="";
						if(isset($parts[1])) {
							$rcv_digits = trim($parts[1]);
							}
						if(($rcv_digits !=="")&&($rcv_digits == $digits)) { } else {	// 自ClientはPlaylistをClearしない
							print "Function:_EventPBoxClose();_RequestPBpx_TblClear();\n";
							}
//						print "Function:alert('Post Recv[".$Type."][".$result_rcv["type"]."]digits[".$digits."][".$result_rcv["message"]."]');\n";
						print "Post UPnP NonStop Interrupt End\n";
						flush();ob_flush();
						return;
						}
					}
				$sleep=$SVR_SLEEP;
				$state = getTransportState($controlUrl,$modename);
				//----- Image Play--------------------------
				if (($imageifg=="on")&&($image=="on")) {					// Image Controel
					if (stripos($state,"STOPPED") !== false){
						if ($stat_stoped >= $SVR_COMSEC) { $endmode="stop";break; }	// EndMode:stop(一定時間超えてた場合は手動とする)
						$stat_stoped=$stat_stoped+$SVR_SLEEP;
						}
					if (stripos($state,"PAUSED") !== false){
						if ($stat_pause >= $SVR_PAUSE) { $endmode="pause";break; }	// EndMode:pause(一定時間超えても次回"PLAYING")
						$stat_pause=$stat_pause+$SVR_SLEEP;
						}
					 $str=$G_IMGDURATION;										// Play,Image Duration
					 $str=explode(".",$str)[0];
					 $image_life=(ArgtimeToSec($str));							// ImageShowTime
					 set_time_limit($image_life+10);							// Status:TRANSITIONING Wait
					 $sleep=$SVR_SLEEP_TRANS;
					 if ($state === "TRANSITIONING") {
						sleep($sleep);											// 秒待機して再度確認
						continue;
						}
					sleep($image_life);											// 一定時間で終了
					$nowcont = getTransportInfo($controlUrl,$modename);			// 再生中のContains
					if ((isset($nowcont['TrackURI']))&&($nowcont['TrackURI']!=="")) {
						$nowplayurl=$nowcont['TrackURI'];
						}
					break;
					}
				//----- Movie & Music Play -------------------
				if (stripos($state,"STOPPED") !== false){						// 停止StatusがCount Timeが遅い場合(FFWは"PLAYING"だがFFWで偶々終了になると"STOPPED"になる)
					if ((isset($nowcont['duration']))&&($nowcont['duration']!=="")) {	// status:"PLAYING"のときは'duration'はあるはずだが "STOPPED"のときはあれば使用
						$str=$nowcont['duration'];										// 'duration'がないときはときは"PLAYING"を使用
						$str=explode(".",$str)[0];
						$musicmeta=ArgtimeToSec($str);							// MusicPlayTime
						}
					$mvr=$musicmeta - $musictime;							// STOPPEDを検出時 Play残範囲であれば,次を再生
					if (($musicmeta > 0)&&($mvr < $SVR_STOPREVISE)) {		// FFWで停止になるケースとSleepの誤差があり得る)
						break;
						}
					}
				if (stripos($state,"STOPPED") !== false){
					if ($stat_stoped >= $SVR_COMSEC) { $endmode="stop";break; }	// EndMode:stop(一定時間超えてた場合は手動とする)
					$stat_stoped=$stat_stoped+$SVR_SLEEP;
					}
				if (stripos($state,"TRANSITIONING") !== false){
					$stat_pause=0;$stat_stoped=0; 	// "STOPPED","PAUSED"は連続をCount STOPPED→TRANSITIONING→STOPPED(これをCount)
					if ($stat_trans >= $SVR_TRANS) { $endmode="Transition Time Ovedr#Clear";break; }	// EndMode:Trans読み込み中(一定時間超えてた場合はHungupとする)
					$stat_trans=$stat_trans+$SVR_SLEEP;
					}
				if (stripos($state,"PAUSED") !== false){
					if ($stat_pause >= $SVR_PAUSE) { $endmode="pause";break; }	// EndMode:pause(一定時間超えても次回"PLAYING")
					$stat_pause=$stat_pause+$SVR_SLEEP;
					}
				if (stripos($state,"PLAYING") !== false){
					$nowcont = getTransportInfo($controlUrl,$modename);			// 再生中のContains
					$stat_pause=0;$stat_stoped=0; 	// "STOPPED","PAUSED"は連続をCount STOPPED(Play開始前)→PLAYING→STOPPED(これをCount)
					if (!is_array($nowcont)) {
						if($trycount < $SVR_TRYCOUNT) {
							$trycount=$trycount+1;
							continue;
							}
						error_log("mpdupnp Server Play (Stop)getTransportInfo Non Array PlayURL[".$playurl."]");
						print "Post UPnP NonStop Smack out\n";
						break;
						}
					$trycount=0;
					if ((isset($nowcont['TrackURI']))&&($nowcont['TrackURI']!=="")) {
						 if (strpos($playurl,$nowcont['TrackURI']) === false) {		// Musicが変わっている
							$hit="";
							foreach ($containers as $key => $ary) {
								if($nowplayurl==$ary["url"]) { $hit="on";break; }	// Playlistにあるかチェック
								}
							if($hit =="") {
								$endmode="Playing Has Changed#Clear";				// Playlistにないので終了
								break;
								} 
							$nowplayurl=$nowcont['TrackURI'];
							}
						 }
					else {
						 error_log("mpdupnp Server Play (Stop) getTransportInfo TrackURI Space PlayURL[".$playurl."]");
						 break;													// NextMusicに移行して見る
						 }
					if ((isset($nowcont['duration']))&&($nowcont['duration']!=="")) {	// PlayTimeによる終了Check
						 $str=$nowcont['duration'];
						 $str=explode(".",$str)[0];
						 $musicmeta=(ArgtimeToSec($str))+2;						// MusicPlayTime
						 set_time_limit($musicmeta+$SVR_PAUSE+10);				// PHP実行時間をMusicPlayTime+_PAUSE監視までを最大値
						 }
					else {
						 error_log("mpdupnp Server Play (Stop) getTransportInfo duration Space PlayURL[".$playurl."]");
						 break;													// NextMusicに移行して見る
						 }
					if ((isset($nowcont['RelTime']))&&($nowcont['RelTime']!=="")) {
						 $str=$nowcont['RelTime'];
						 $relsec=ArgtimeToSec($str);
						 $defsec=(abs($musictime-$relsec));
						 if($defsec > $SVR_LOSTSEC) {							// 予想PlayTimeとぼ誤差(別PCで同じ曲を選択した?
							print "Post UPnP NonStop Lost,PlayTime Operated by another PC\n";
							flush();
							ob_flush();
							}
						 $musictime=ArgtimeToSec($str);
						 }
					if($musictime >= $musicmeta) { break; }	// Music Play End (通常の終了判定)
					$sleep=$musicmeta-$musictime;				// SleepTimeを決める
					}
				if ($sleep > $SVR_SLEEP) {						// Play continue
					$sleep=$SVR_SLEEP;
				 	}
				else if($sleep <= 0) { break; }
				sleep($sleep);									// 秒待機して再度確認
				if ($state === "PLAYING") {
					$musictime=$musictime+$sleep;				// Sleep時間を加算しmusictime(PlayTime)にする
					}
//				$fp=fopen("log/mpdupnp.log","a");$now=date("Y/m/d H:i:s");fwrite($fp,$now." Sleep URL(".$playurl.")getTransportState(".$state.') $musictime('.$musictime.')$musicmeta('.$musicmeta.")stat_pause(".$stat_pause.")\n");fclose($fp);
				}
// -------- Next Music Search -----------------------------------------------------------
			if ($endmode !=="") { break; }						// 強制終了
			$hit="";											// 今回Playの次のMusicを探す
			foreach ($containers as $key => $ary) {
				if($ary["Mode"]=="contns") { continue; }		// Contains
				if($ary["Mode"] !=="contns") {
					$ext=substr($ary["url"], strrpos($ary["url"], '.') + 1);
					$ext=strtolower($ext);
					if ((in_array($ext,$G_EXCLUDEXT))&&($image=="off"))  { continue; }		// Renderer Image"off"時、拡張子が画像のとき対象外
					}
				if($nowplayurl==$ary["url"]) { $hit="on";continue; }							// 再生中の曲までSkip
				if ($hit !=="on") { continue; }
				$playurl=$ary["url"];							// NextMusic
				$playid=$ary["id"];
				$hit="next";
				break;
				}
			if ($hit !=="next") { break; }						// Nextが見つからないか,(最後か変更されたとき)
			}
		}
// -------- End Proc---------------------------------------------------------------------
	if ($imageifg=="on") {										// Image End
		$stopParams = array(									// Stop送信
			"InstanceID" => 0,
			);
		sendUpnpRequest($controlUrl, "AVTransport", "Stop" , $stopParams,$modename);
		}
	$str="";
	if($endmode !== "") {					// Format,$endmpde:"Status|Message#PlayboxCommand",Status:stop/pause
		$str="Interrupt(".$endmode.") ";						// 強制停止: JS Queue Clear,PlayBox:Off
		$msg=explode("#",$endmode);
		$stopfunc="";
		if ($msg[0]=="stop") {									// Stop時,時計マークをクリア
			$stopfunc="RequestUnLoadDisp();";
			}
		if(($msg[0]=="stop")||($msg[0]=="pause")) {				// StatusかRendererPlaylistか?
			 $submsg="Command Stop due to time lapse";
			 }
		else {
			 $submsg="Renderer Change Detection";
			 }
		if ((isset($msg[1]))&&($msg[1] !=="")) {
			 print "Function:".$stopfunc."_EventPBoxClose();RequestPBoxPlay('".$msg[1]."');RequestMsgBox('UPnP:WebUI UPnP ServerPlay','Renderer[".$msg[0]."]<BR>".$submsg."');\n";
			 }
		else {
			 print "Function:".$stopfunc."_EventPBoxClose();RequestPBoxPlay('Clear');RequestMsgBox('UPnP:WebUI UPnP ServerPlay','Renderer[".$msg[0]."]<BR>".$submsg."');\n";
			 }
		}
	print "Post UPnP NonStop ".$str."End\n";
//	$fp=fopen("log/mpdupnp.log","a");$now=date("Y/m/d H:i:s");fwrite($fp,$now." UPnP ServerPlay End\n");fclose($fp);
	flush();
	ob_flush();
	}

/* ****************************************************************************
	Renderer Request
	I/F Renderer
	 getTransportState(): Renderer , Get Status
	 getTransportInfo() : Renderer , Get Meta)
	※SSDP Reuestは, 後半Renderer UPnP Request参照
***************************************************************************** */
/* -----------------------------------------------------------------------------
	getTransportState($controlUrl,$modename) : Renderer , Get Status
	$controlUrl: i  : AVTransportのURL
	$modename  : i  : Render ModeName
	@return    : o  : string 再生状態 ("PLAYING", "STOPPED", "PAUSED_PLAYBACK","NO_MEDIA_PRESENT") 取得不可:"UNKNOWN"
	                      Imageの場合: "TRANSITIONING":転送中
	※ modeName=Render+"|"+modeName; modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
----------------------------------------------------------------------------- */
function getTransportState($controlUrl,$modename="") {
	$params = array("InstanceID" => "0");
	$response = sendUpnpRequest($controlUrl, "AVTransport", "GetTransportInfo", $params,$modename);
//	$now = date("H:i:s");file_put_contents("./log.txt",$now." getTransportState:".$response."\n", FILE_APPEND);
	if (!empty($response)) {							// XMLレスポンスを解析してCurrentTransportStateを取得
		preg_match('/<CurrentTransportState.*?>(.*?)<\/CurrentTransportState>/s', $response, $matches);
		if (isset($matches[1])) {
			$status=$matches[1];
			return($status);
		   	}
		}
	return("into...");									// 状態が取得できない場合
	}

/* -----------------------------------------------------------------------------
	getTransportInfo($controlUrl,$modename="") : Renderer , Get Meta (Used , NowPlay,PlayBox)
	$controlUrl: i  : AVTransportのURL
	$modename  : i  : Render ModeName
	@return    : o  : Array:Playing contains
  ※return:$containers[{Meta}],
	                  [Track]  :{n},[TrackDuration]:{h:mm.ss} , [TrackURI]:{URL}
	                  [RelTime]:{h:mm.ss},[AbsTime]:{h:mm.ss} , [RelCount]:{n}
	※ modeName=Render+"|"+modeName; modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
----------------------------------------------------------------------------- */
function getTransportInfo($controlUrl,$modename="") {
	$params = array("InstanceID" => "0");
	$response = sendUpnpRequest($controlUrl, "AVTransport", "GetPositionInfo", $params,$modename);
	if($response === false) { return(false); }
//	print $response."<BR>";
	preg_match('/<TrackMetaData.*?>(.*?)<\/TrackMetaData>/s', $response, $matches);
	if (isset($matches[1])) {
		if ((stripos($matches[1],"null") === false)&&($matches[1] !=="")) {
			$resultDecoded=htmlspecialchars_decode($matches[1]);					// HTMLエンティティのデコード
			$metaxml = simplexml_load_string($resultDecoded);
			$namespaces = $metaxml->getNamespaces(true);							// 名前空間を登録
			$element = $metaxml->children($namespaces['DIDL-Lite']);
			if ($element->getName() === "item") {
				$childrenDc = $element->children($namespaces['dc']);
				$childrenUpnp = $element->children($namespaces['upnp']);
				$childrenDlna = $element->children($namespaces['dlna']);
				$containers = [										// 各要素を配列に格納 再生中なので配列として扱わない
					"id" => (string)$element[0]['id'],
					"parentID" => (string)$element[0]['parentID'],
					"title" => (string)$childrenDc->title,
					"artist" => (string)$childrenUpnp->artist,
					"creator" => (string)$childrenDc->creator,
					"album" => (string)$childrenUpnp->album,
					"originalTrackNumber" => (string)$childrenUpnp->originalTrackNumber,
					"ArtURI" => (string)$childrenUpnp->albumArtURI,
					"genre" => (string)$childrenUpnp->genre,
					"class" => (string)$childrenUpnp->class,
					"date" => (string)$childrenDc->date,
					"url" => (string)$element[0]->res,
					"protocolInfo" => (string)$element[0]->res['protocolInfo'],
					"duration"  => (string) $element[0]->res['duration'],
					];
				}
			}
		}
	foreach ($containers as $key => &$val) {
		if($val=='""') { $val=""; }									// 空:「""」を''にする
		$val=str_replace(["\r", "\n"],"", $val);					// Item中に"\r","\n"があるので''にする
		}
	preg_match('/<Track.*?>(.*?)<\/Track>/s', $response, $matches);
	if (isset($matches[1])) {
		if ((stripos($matches[1],"null") === false)&&($matches[1] !=="")) {
			$containers["Track"]=$matches[1];
			}
		}
	preg_match('/<TrackDuration.*?>(.*?)<\/TrackDuration>/s', $response, $matches);		// PLAYING中で応答
	if ((isset($matches[1]))&&(($matches[1] !== "null")&&($matches[1] !==""))) {
		$containers["TrackDuration"]=$matches[1];
		}
	preg_match('/<TrackURI.*?>(.*?)<\/TrackURI>/s', $response, $matches);
	if (isset($matches[1])) {
		if ((stripos($matches[1],"null") === false)&&($matches[1] !=="")) {
			$containers["TrackURI"]=$matches[1];
			}
		}
	preg_match('/<RelTime.*?>(.*?)<\/RelTime>/s', $response, $matches);
	if (isset($matches[1])) {
		if ((stripos($matches[1],"null") === false)&&($matches[1] !=="")) {
			$containers["RelTime"]=$matches[1];
			}
		}
	preg_match('/<AbsTime.*?>(.*?)<\/AbsTime>/s', $response, $matches);
	if (isset($matches[1])) {
		if ((stripos($matches[1],"null") === false)&&($matches[1] !=="")) {
			$containers["AbsTime"]=$matches[1];
			}
		}
	preg_match('/<RelCount.*?>(.*?)<\/RelCount>/s', $response, $matches);
	if (isset($matches[1])) {
		if ((stripos($matches[1],"null") === false)&&($matches[1] !=="")) {
			$containers["RelCount"]=$matches[1];
			}
		}
//	$now = date("H:i:s");ob_start();print_r($containers);$buf=ob_get_contents();ob_end_clean();file_put_contents("./log.txt",$now." URL[".$DirectoryUrl."] id[".$id."] Array(".$total."):".$buf."\n", FILE_APPEND);
	return($containers);
	}
/* Renderer Rresponse Image ------------------------------------------------------------
HTTP/1.1 200 OK
CONTENT-LENGTH: 1570
CONTENT-TYPE: text/xml; charset="utf-8"
DATE: Mon, 17 Mar 2025 05:17:07 GMT
EXT:
SERVER: {ServerName}, UPnP/1.0, DLNADOC/1.50

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetPositionInfoResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<Track>1</Track>
<TrackDuration>0:05:19.530</TrackDuration>
<TrackMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot;
 xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot; xmlns:dlna=&quot;urn:schemas-dlna-org:metadata-1-0/&quot;&gt;&lt;item id=&quot;64$0$F$6$3$0&quot; parentID=&quot;0&quot; restricted=&quot;1&quot; searchable=&quot;0&quot;&gt;&lt;dc:title&gt;TRUTH (Bossa Nova Version)&lt;/dc:title&gt;
&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;upnp:album&gt;東京Jazz 2007&lt;/upnp:album&gt;&lt;upnp:originalTrackNumber&gt;1&lt;/upnp:originalTrackNumber&gt;&lt;upnp:albumArtURI&gt;http://192.168.0.200:8200/AlbumArt/3766-19617.jpg&lt;/upnp:albumArtURI&gt;
&lt;res protocolInfo=&quot;http-get:*:audio/x-flac:*&quot; duration=&quot;0:05:19.530&quot;&gt;http://192.168.0.200:8200/MediaItems/19617.flac&lt;/res&gt;&lt;dc:creator&gt;ANMI2/安藤正容&lt;/dc:creator&gt;&lt;upnp:artist&gt;ANMI2/安藤正容&lt;/upnp:artist&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</TrackMetaData>
<TrackURI>{Playing URL}</TrackURI>
<RelTime>00:00:36</RelTime>
<AbsTime>00:00:00</AbsTime>
<RelCount>2147483647</RelCount>
<AbsCount>2147483647</AbsCount>
</u:GetPositionInfoResponse>
</s:Body>
</s:Envelope>
-------------------------------------------------------------------------------*/

/*******************************************************************************
	Function ftok (Communication API)
	System V IPC IPC Key (プロセス間通信キー)
	 php.ini: extension=sysvmsgをArgFtokOpen()で行う
	 1. ArgFtokOpen()   : Open QueueKey生成
	 2. ArgFtokRemove() : Queueを空にする
	 3. ArgFtokSend()   : Queue Put
	 4. ArgFtokReceive(): Queue Get
*******************************************************************************/
/*
	ArgFtokOpen() : ftok Open
	$quekey : i  : QueueKeyを指定 : 任意の文字列 , 指定なし時:__FILE__(自身のファイル名)
	               同じQueueKeyで送受できる。
	@return : o  : Descriptor
*/
function ArgFtokOpen($quekey="") {
	if (!extension_loaded('sysvmsg') && function_exists('dl')) {
		@dl('sysvmsg.so');
		}
	if($quekey=="") {
		 $fpftok = ftok(__FILE__, 'a');
		 }
	else {
		 $fpftok = crc32($quekey);
		 $fpftok = sprintf('%u', $fpftok);	// 符号なし文字列化
		 $fpftok = (int)$fpftok; 			// 整数化
		 }
	return($fpftok);
	}

/*
	ArgFtokSend($fpftok,$message) : ftok Send
	$fpftok : i  : Open Descriptor
	$message: i  : Send Mseeage
	$type   : i  : Message Type (任意) default:1
	@return : o  : Result Array ["type"]:$msgtype, ["message"]:$message
	               False,result["message"]:false
*/
function ArgFtokSend($fpftok,$message,$type=1) {
	$result["message"] = null;
	$result["type"]    = 0;
	$queue = msg_get_queue($fpftok);	// キュー取得
	if (msg_send($queue,$type,$message)) {
		$result["message"] = $message;	// 成功時は送信したメッセージを返す
		$result["type"] = $type;
		}
    return $result;
	}

/*
	ArgFtokReceive($fpftok,$type,$length) : ftok Receive
	$fpftok : i  : Open Descriptor
	$type   : i  : $msgtype : 同じTypeは自身のQueueとしてQueueに戻す。0:指定時,自身Queueの扱いはしない。
	$length : i  : Buffer Length default:1024
	@return : o  : Result Array ["type"]:$msgtype, ["message"]:$message
	               $msgtype:ArgFtokSendで設定した値
	               False,result["message"]:false
*/
function ArgFtokReceive($fpftok,$type=0,$length=1024) {
	$queue = msg_get_queue($fpftok);	// キュー取得
	$message = null;
	$msgtype = 0;
	msg_receive($queue,0,$msgtype,$length, $message, true, MSG_IPC_NOWAIT);	// メッセージ受信
	if (($type !== 0)&&($msgtype == $type)) {
		 ArgFtokSend($fpftok,$message,$msgtype);
		 $result = ["type" => 0, "message" => null];
		 }
	else if($msgtype ==0){
		 $result = ["type" => 0, "message" => null];
		 }
	else {
		 $result = ["type" => $msgtype, "message" => $message];
		 }
	return($result);
	}

/*
	ArgFtokRemove($fpftok,$length) : ftok Remove
	$fpftok : i  : Open Descriptor
	$length : i  : Buffer Length default:1024
*/
function ArgFtokRemove($fpftok,$length=1024) {
	$queue = msg_get_queue($fpftok);	// キュー取得
	if(!msg_remove_queue($queue)) {;			// キュー自体を削除
		print "Function:alert('msg_remove_queuefalse');\n";
		}
	}

/*******************************************************************************
	HTML Definition
*******************************************************************************/
$SCR_WIDTH=490;

echo <<<EOM
<html lang="ja"><link rel="shortcut icon" href="./mpdwebfavicon.ico"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" ><meta http-equiv="Expires" content="1000">
<meta id="idMeta" name="viewport" name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.7">
<script type="text/javascript">
var POSTMOD="{$_SERVER['PHP_SELF']}";		// POST ServerModule
var HEIGHT_DEF= {$G_RENDERHEIGHT_DEF};		// MediaServer部以Heigth : Window size - HEIGHT_DEF で求める
var GSOCTIMEOUT={$G_TIMEOUT_CONTENTS};
var GBROWSEMAX={$G_BROWSEMAX};				// Content Browse Max
var GPLAYMAX={$G_PLAYMAX};					// Content Play Max

var GSERVERPLAY="{$G_SERVERPLAY}";			// ServerPlay Default

/* View Port 設定 */
var getDevice = (function(){
		var ua = navigator.userAgent;
		if(ua.indexOf('iPhone') > 0 || ua.indexOf('iPod') > 0 || ua.indexOf('Android') > 0 && ua.indexOf('Mobile') > 0){
			 return 'sp';
			 }
		else if(ua.indexOf('iPad') > 0 || ua.indexOf('Android') > 0){
			 return 'tab';
			 }
		else {
			 return 'other';
			 }
		})();
if(window.outerWidth==0) {											// window.outerWidthが0になるケースがある。(反則的な対応)
	window.outerWidth=window.innerWidth;
	}
if ((getDevice == 'sp' )||(getDevice == 'tab' )){
	 scale=Math.floor((window.outerWidth/{$SCR_WIDTH})*100)/100;	// 幅比率で表示(小数3位切り捨て)
	 var viewportContent="width=380,initial-scale="+String(scale)+",maximum-scale=1.7";
	 document.getElementById('idMeta').setAttribute('content',viewportContent);
	 }
</script>

EOM;

?>
<script type="text/javascript">
/* ****************************************************************************
	AJAX RequestByPost()
    "\n"までで動作の区切りとしている点は同じなので"\n"を間にいれないこと。
 ※状来のRequestByPostを改良している。
   Error時のCallbackについては、Libraryと違い行っていない。
   WebPageのBack(Historylocation.href)にAJAX時のURLを登録している。
   mpdweb組込み版はhdml時,html-idを"grp*****"としているが制約を解除している。(mpdweb.libcajax.js同様)
 ※全体的なVersionの場合このI/Fをベースにする。
******************************************************************************/
var ERSTR = 'ERR';				// StanderdOut:Error時のString
var IDSTS = 'stdout';			// StanderdOut:を表示するErea名
/* ----------------------------------------------------------------------------
	RequestByPost(module,para,callback) : Post Request
		module  : i  : Post Module
		para    : i  : Post Parameter
		callback: i  : Callback
	 Post reception format
	  Value: {string}={string} : Set constant in JS item JS定数設定
	  Function:{function}      : Performing JS functions JS関数実行
	  {HTML ID}]:{Display-HTML}: HTML ID Display
	  String                   : StanderdOut,Status Line Display
---------------------------------------------------------------------------- */
function RequestByPost(module,para,callback) {
	if(window.addEventListener) {									// Firefox
		  var request = new XMLHttpRequest();						// Ajax Request Function
		  }
	 else {															// IE
		  var request =  new ActiveXObject("Microsoft.XMLHTTP");	// Ajax Request Function
		  }
	document.getElementById(IDSTS).innerHTML="";
	/* ステータス( 読み込み中なのか完了したのか) が変更されたら readyStateChangeHandler を実行 */
	request.open("POST", module , true);
	request.onreadystatechange = readyStateChangeHandler;
	request.setRequestHeader( "Content-Type" ,  "application/x-www-form-urlencoded");
	request.send(para);
	function readyStateChangeHandler(){								// Receive Completion
		switch(request.readyState){
			case 4:
				if(request.status == 200) {							// 完了時
//console.log("Response:", request.response);
					var res_ans = request.response.split("\n");
					var response = "";var last_msg = "";var err_msg = "";
					for(var i=0; i < res_ans.length ; i++ ) {
						if (res_ans[i] == "") {  continue; }
						var htm_id = res_ans[i].split(":",2);
						var htm_id_str = res_ans[i].replace(htm_id[0]+":","");
					// Form : Value Value:が検出されたときは，変数の変更 : "^"先頭一致
						if ((htm_id.length>=2)&&(htm_id[0].match(RegExp("^value","i")))) {		// Receive: Value: {string}={string}
							var valstr = htm_id[1].split("=",2);
							if (valstr.length >=2) {
								var chgstr=res_ans[i].replace(htm_id[0]+":"+valstr[0]+"=","");	// splitは切り捨てのためValue:{string}を""に置換
								eval(valstr[0] + "= "+chgstr);
								continue; 
								}
							}
					// Form : Function Function:が検出されたときは，以下をFunctionと扱う : "^"先頭一致
						if (res_ans[i].match(RegExp("^Function","i"))) {						// Receive: Function:{function}
							try {
								eval(htm_id_str);
								} catch (e) {
								response=response+"Callback:Errror "+e+"\n";					// エラーを検知
								}
							continue;
							}
					// Form : {HTML ID}]:{Display} HTML IDが存在時に表示
						if (htm_id.length>=2) {
							if (document.getElementById(htm_id[0]) != null) {					//  HTML-IDがあれば表示する。
								var idobj = document.getElementById(htm_id[0]);
								idobj.outerHTML=htm_id_str;
								continue;
								}
							}
						response=response+res_ans[i]+"\n";			// 形式なしはメッセージとする
						if  (res_ans[i].match(RegExp(ERSTR,"i"))) {	// 引数のError MessageWordと一致？
							 err_msg  = res_ans[i]; }
						else {
							 last_msg = res_ans[i]; }
						}
				// Case End Process
					var out = document.getElementById(IDSTS);
					if (err_msg != "") {							// 先頭はErrorの行を優先し表示
					 	 out.innerHTML = err_msg  + "\n"; }
					else {
						 out.innerHTML = last_msg + "\n"; }
					out.innerHTML += response;
				// Call Back
					if (typeof callback === "function") {
						try {
							callback();
							} catch (e) {
  							console.error("Callback error:", e);
							}
						}
					RequesHistory(location.href);		// Make Post,History (Orignal,mpdupnp.php)
					}
			break;
			}
		}
	}

/* ----------------------------------------------------------------------------
	RequesHistory(url): Post UPnPDevice Get : From,RequestByPost()
	url   : i  : History Return URL
----------------------------------------------------------------------------- */
function RequesHistory(url) {
	var xhr = new XMLHttpRequest();
		xhr.open('GET', url, true);
		xhr.onreadystatechange = function() {
		if (xhr.readyState === 4 && xhr.status === 200) {
			if (window.history && window.history.pushState) {
				history.pushState("Post", null, url);
				}
			}
		};
	xhr.send();
	}

/*
	HistoryBack 
*/
window.addEventListener('popstate', function(event) {
	if (event.state) {
		if (event.state == "Post") {
			if (document.getElementById("idBack")) {
				 document.getElementById("idBack").click();
				 }
			}
		}
	});

history.pushState("Top",null,location.href);
/* ****************************************************************************
	Function UPnP Contents Server
	ServerRequestI/F
	 EventUPnPGet()         :SSDP Device , Used UPnPDeviceList
	 EventUPnPDevices()     :SSDP Request
	 EventUPnPDCont()       :Make UPnP Contains
	 EventRendererManager() :ConnectionManager Request(Server対応をGet)
	※CommonFunctionは概要除く
***************************************************************************** */
<?php
global $G_MAKESSDP;
echo <<<EOM
var GMAKESSDP="{$G_MAKESSDP}";

EOM;
?>
/* ----------------------------------------------------------------------------
	EventUPnPGet(getmode) : Post UPnPDevice Get
	getmode : i  : 1:Make UPnPDeviceList , 2:Read UPnPDeviceList
----------------------------------------------------------------------------- */
function EventUPnPGet(getmode) {
	RequestLoadDisp();
	RequestByPost(POSTMOD,'p=upnpget&getmode='+getmode+'&makessdp='+GMAKESSDP,function(){ RequestUPnPReset();RequestUnLoadDisp(); });
	_EventPBoxClose();			// PlayPanelBox Close
	}

/* ----------------------------------------------------------------------------
	EventUPnPDevices(url,id,bid) : Event UPnP Device Click
	url   : i  : ssdpUrl
	id    : i  : ssdpid
	bid   : i  : Back ssdpid
----------------------------------------------------------------------------- */
function EventUPnPDevices(url,id,bid) {
	url=encodeURIComponent(url);
	if (id == null) {
		 var pstr='p=upnpcontents&url='+url;
		 }
	else {
		 id=encodeURIComponent(id);
		 var pstr='p=upnpcontents&url='+url+'&id='+id;
		 if (bid == null) { } else {
			 bid=encodeURIComponent(bid);
			 pstr=pstr+'&bid='+bid;
			 }
		 }
	var cname=encodeURIComponent(document.getElementById('idSelCName').textContent);
	var image=document.getElementById('idSelImage').textContent
	cname='&cname='+cname+"&image="+image;
	pstr=pstr+cname;
	RequestLoadDisp();
	RequestByPost(POSTMOD,pstr,function(){ RequestUnLoadDisp(); });
	}

/* ----------------------------------------------------------------------------
	EventUPnPDCont(id,bid,cont_no) : Event UPnP Contains Click 
	id       : i  : ssdpid
	bid      : i  : Back ssdpid
	cont_no  : i  : Content No : コンテンス数)省略時チェックしない
----------------------------------------------------------------------------- */
function EventUPnPDCont(id,bid,cont_no) {
	if (cont_no == null) {
		cont_no="";
		}
	if(cont_no !=="") {
		var no=parseInt(cont_no);
		if(no == 0) {
			RequestMsgBox('UPnP:WebUI UPnP Contains','There is no content in the specified hierarchy');
			return;
			}
		if(no > GBROWSEMAX) {
			RequestMsgBox('UPnP:WebUI UPnP Contains','Specified content exceeded (Max:'+GBROWSEMAX+')');
			return;
			}
		}
	var url = document.getElementById('idSelUPnPURL').textContent;
	url=encodeURIComponent(url);
	if (id == null) {
		 var pstr='p=upnpcontents&url='+url;
		 }
	else {
		 id=encodeURIComponent(id);
		 var pstr='p=upnpcontents&url='+url+'&id='+id;
		 if (bid == null) { } else {
			 bid=encodeURIComponent(bid);
			 pstr=pstr+'&bid='+bid;
			 }
		 }
	var cname=encodeURIComponent(document.getElementById('idSelCName').textContent);
	var image=document.getElementById('idSelImage').textContent
	cname='&cname='+cname+"&image="+image;
	pstr=pstr+cname;
	RequestLoadDisp(GSOCTIMEOUT);
	RequestByPost(POSTMOD,pstr);
	}

/* ----------------------------------------------------------------------------
	EventUPnPDisp(server,modelName) : Media Server:FriendlyName Display
	server    : i  : FriendlyName
	modelName : i  : Renderer種類Model
	※ modelName:Server+"|"+Name; ServerName:機種名だけになってしまうので,Server(FriendlyName)+"|"+ServerName
----------------------------------------------------------------------------- */
function EventUPnPDisp(server,modelName) {
	var str="MediaServer :"+server;
	document.getElementById('idServName').textContent=str;
	document.getElementById('idSelCName').textContent=server+'|'+modelName;
	}

/* ----------------------------------------------------------------------------
	EventRenderer(row) : Media Server:RendererSelect(FriendlyName) Display
	row    : i  : Select UPnP Renderer Row
----------------------------------------------------------------------------- */
function EventRenderer(row) {
	EventRendererClear();
	document.getElementById('idSelRow').textContent=row;	
	var idreder="Renderer_"+row;
	document.getElementById('idSelcontUrl').textContent=document.getElementById(idreder).textContent;
	var idmanager="Manager_"+row;
	document.getElementById('idManagerUrl').textContent=document.getElementById(idmanager).textContent;
	var idmode="modelName_"+row;
	document.getElementById('idSelmodelName').textContent=document.getElementById(idmode).textContent;
	EventRendererRowSet();
	var objRenderTb=document.getElementById('idRenderTB');
	var str=objRenderTb.rows[row*2].cells[1].textContent;
	document.getElementById('idSelRender').textContent=str;	

	EventRendererManager(document.getElementById('idManagerUrl').textContent);
	}

/*
	EventRendererClear() : Media Server: RendererSelect Clear
*/
function EventRendererClear() {
	var idSelRowObj=document.getElementById('idSelRow');
	if(idSelRowObj.textContent =="") { return; }
	var row=idSelRowObj.textContent;
	_EventRendererRowSet("reset",row);
	document.getElementById('idSelRender').textContent="";
	document.getElementById('idSelcontUrl').textContent="";
	document.getElementById('idManagerUrl').textContent="";
	document.getElementById('idSelRow').textContent="";	
	document.getElementById('idSelmodelName').textContent="";
	}

/*
	EventRendererRowSet() : Media Server: RendererSelect RowSet
*/
function EventRendererRowSet() {
	var idSelRowObj=document.getElementById('idSelRow');
	if(idSelRowObj.textContent =="") { return; }
	var row=idSelRowObj.textContent;
	var idreder="Renderer_"+row;
	if(document.getElementById('idSelcontUrl').textContent == document.getElementById(idreder).textContent) {
		 _EventRendererRowSet("set",row);
		 }
	else {
		 _EventRendererRowSearch()
		 }
	}

/*
	_EventRendererRowSearch() : Media Server: RendererSelect Search RowSet (Header&Table一致Set)
*/
function _EventRendererRowSearch() {
	var objRenderTb=document.getElementById('idRenderTB');
	for (var i=0; i < objRenderTb.rows.length/2;i++) {
		var idreder="Renderer_"+i;
		if(document.getElementById('idSelcontUrl').textContent == document.getElementById(idreder).textContent) {
			 _EventRendererRowSet("set",i);
			 }
		else {
			 _EventRendererRowSet("reset",i);
			 }
		}
	}

/*
	_EventRendererRowSet(mode,row) : Renderer Table Select Set/Reset
	mode   : i  : set:Select Table Set / reset(etc):Select Table Reet
	row    : i  : Select RendererTable
*/
function _EventRendererRowSet(mode,row) {
	var objRenderTb=document.getElementById('idRenderTB');
	if(mode =="set") {
		 objRenderTb.rows[row*2].style.backgroundColor="#191970";
		 objRenderTb.rows[row*2+1].style.backgroundColor="#191970";
		 EventNowMusic();		// Timer監視Start
		 }
	else {
		 objRenderTb.rows[row*2].style.backgroundColor="";
		 objRenderTb.rows[row*2+1].style.backgroundColor="";
		 }
	}

/* ----------------------------------------------------------------------------
	EventRendererManager(url) : connectionManager Request
	url    : i  : connectionManagerControlUrl
----------------------------------------------------------------------------- */
function EventRendererManager(url) {
	var manageurl=encodeURIComponent(url);
	RequestByPost(POSTMOD,'p=upnpmanager&url='+manageurl);
	}

/* ****************************************************************************
	Function UPnP Renderer
	ServerRequestI/F
	 EventPlayUPnPReq()  :ContentsServer,Contents Post:Play
	 EventPlayPLReq()    :mpdupnp PlaylistQueue Request Post:Play
	 EventStateRequest() :Renderer Post:Status Post:Playinfo
***************************************************************************** */

/* ----------------------------------------------------------------------------
	EventPlayUPnPReq(queue,mediaurl,id,cont_no) : UPnP Request Post:Play Cheack
	queue   : i  : file / dir
	mediaurl: i  : ssdpUrl (CurrentURI)
	id      : i  : ssdp id
	cont_no : i  : Content No : コンテンツ数)省略時チェックしない
----------------------------------------------------------------------------- */
function EventPlayUPnPReq(queue,mediaurl,id,cont_no) {
	if (cont_no == null) {
		cont_no="";
		}
	if(cont_no !=="") {
		var no=parseInt(cont_no);
		if(no == 0) {
			RequestMsgBox('UPnP:WebUI UPnP Request','There is no content in the specified hierarchy');
			return;
			}
		if(no > GPLAYMAX) {
			RequestMsgBox('UPnP:WebUI UPnP Request','Specified Request exceeded (Max:'+GPLAYMAX+')');
			return;
			}
		}
	var Render=document.getElementById('idSelRender').textContent;
	if(Render =="") {
		RequestMsgBox('UPnP:WebUI UPnPRequest','Select the Renderer and then Play.');
		return;
		}
	var url = document.getElementById('idSelUPnPURL').textContent;
	var modeName=document.getElementById('idSelmodelName').textContent;
	modeName=Render+"|"+modeName;					// modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
	_EventPlayUPnPReqPost(queue,url,mediaurl,id,modeName);
	}

// Server Play Mode cheack:on Else :off | ""
// GSERVERPLAY:PHPで設定したCheckBoxのDefault,SERVERPLAYはCheckBoxのCheckのGlobal
var SERVERPLAY="";
var DIGITS=ArgRandomDigits(8);
/* ----------------------------------------------------------------------------
	_EventPlayUPnPReqPost(queue,url,mediaurl,id,modeName) : UPnP Request Post:Play
	queue   : i  : file / dir
	url     ; i  : ssdpURL(CurrentURI)
	mediaurl: i  : ssdp mediaURL (CurrentURI)
	id      : i  : ssdp id
	modeName: i  : Renderer種類Model
	※ modelName:Server+"|"+Name; ServerName:機種名だけになってしまうので,Server(FriendlyName)+"|"+ServerName
----------------------------------------------------------------------------- */
function _EventPlayUPnPReqPost(queue,url,mediaurl,id,modeName) {
	var urlconv =encodeURIComponent(url);
	var conturl=document.getElementById('idSelcontUrl').textContent;

	conturl=encodeURIComponent(conturl);
	if(queue=="file") {
		 RequestLoadDisp(1);
		 mediaurl=encodeURIComponent(mediaurl);
		 id=encodeURIComponent(id);
		 var pid=document.getElementById('idSelPid').textContent;
		 pid=encodeURIComponent(pid);
		 modeName=encodeURIComponent(modeName);
		 RequestByPost(POSTMOD,'p=upnpreq&url='+urlconv+'&conturl='+conturl+"&pid="+pid+"&id="+id+"&mediaurl="+mediaurl+"&modename="+modeName,function(){ _EventPlayStartEvent(); });
		 _RequestPBpx_TblClear();				// PB Playlist Table Clear
		 }
	else {
		 if(document.getElementById('idServer').checked) {		// Server PlayMode
			  SERVERPLAY="on";
			  }
		 else {
			  SERVERPLAY="off";
			  }
		 id=encodeURIComponent(id);				//JSでPlayBoxHeaderを表示
		 modeName=encodeURIComponent(modeName);
		 var image=document.getElementById('idSelImage').textContent
		 RequestLoadDisp(5);
		 RequestByPost(POSTMOD,'p=upnpqueue&url='+urlconv+"&id="+id+'&conturl='+conturl+"&image="+image+"&modename="+modeName+"&serverplay="+SERVERPLAY+"&digits="+DIGITS,function(){ RequestUnLoadDisp();EventNowMusic_Header(1);_EventPlayStartEvent(); });

		 document.getElementById('idPBUrl').textContent=url;	// ServerPlay Process再起動ように保持
		 }
   console.log("queue["+queue+"]URL["+url+"]mediaurl["+mediaurl+"]id["+id+"]ContURL["+conturl+']'+"]modeName["+modeName+"]");
	}

/*
	ArgRandomDigits(len) : Random生成(Client識別にする。Browser:Tabまで識別する)
	len     : i  : Random生成Length
	@return : o  : RandomValue
*/
function ArgRandomDigits(len = 8) {
	const array = new Uint8Array(len);      // {len}ByteArray (0〜255)
	crypto.getRandomValues(array);          // Random値を格納
	return Array.from(array, b => (b % 10).toString()).join('');
	}

/* ----------------------------------------------------------------------------
	EventPlayPLReq(no,url,pid,mediaurl,id) : UPnP Playlist Request Post:Play
	no      : i  : Track.no
	url     : i  : ssdpUrl
	pid     : i  : file / dir
	mediaurl: i  : ssdpUrl (CurrentURI)
	id      : i  : ssdp id
----------------------------------------------------------------------------- */
function EventPlayPLReq(no,url,pid,mediaurl,id) {
	var Render=document.getElementById('idSelRender').textContent;
	if(Render =="") {
		RequestMsgBox('UPnP:WebUI EventPlayPLReq','Select the Renderer and then Play.');
		return;
		}
	EventNowMusic_Header(no);
	var urlconv =encodeURIComponent(url);
	var conturl=document.getElementById('idSelcontUrl').textContent;
	conturl=encodeURIComponent(conturl);
	mediaurl=encodeURIComponent(mediaurl);
	id=encodeURIComponent(id);
	pid=encodeURIComponent(pid);
	var modeName=document.getElementById('idSelmodelName').textContent;
	modeName=Render+"|"+modeName;					// modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
	modeName=encodeURIComponent(modeName);

	var rensts=document.getElementById('idStatus').textContent;
	if((rensts.toUpperCase()=="STOPPED")&&(SERVERPLAY=="on")) {		// Play 停止でServerProcessが終了するための対処。
		 var image=document.getElementById('idSelImage').textContent;
		 var url=document.getElementById('idPBUrl').textContent;
		 RequestLoadDisp(5);										// DirModeでPostする
		 RequestByPost(POSTMOD,'p=upnpqueue&url='+urlconv+"&id="+pid+'&playid='+id+'&conturl='+conturl+"&image="+image+"&modename="+modeName+"&serverplay="+SERVERPLAY+"&digits="+DIGITS,function(){ RequestUnLoadDisp();_EventPlayStartEvent();_EventUPnPLPlayinfo(conturl,modeName); });
	 	 }
	else {											// 再生中は、ClientでContentsをフックする
		 RequestByPost(POSTMOD,'p=upnpreq&url='+urlconv+'&conturl='+conturl+"&pid="+pid+"&id="+id+"&mediaurl="+mediaurl+"&modename="+modeName,function(){ _EventPlayStartEvent();_EventUPnPLPlayinfo(conturl,modeName); });
   		 console.log("EventPlayPLReq post;upnpreq CallBack:_EventPlayStartEvent();_EventUPnPLPlayinfo("+conturl+","+modeName+"); }");
		}
	}

/*
	_EventPlayStartEvent() : EventNowMusic TimerClear ReStart("PLAYING"から監視をStart)
*/
function _EventPlayStartEvent() {
	GPLTime=0;var stsstr="";
	if (document.getElementById('idStatus')) {				// Pauseのとき,StopTimerを解除後のLoopを補助
		GStatus=document.getElementById('idStatus').textContent;
		stsstr=GStatus.toUpperCase();
		}
	if (stsstr.toUpperCase() === "PLAYING") { } else {			// PlayTimer
		GStatus.textContent="PLAYING";
		}
	EventNowMusic_Restart();
	}

/* ----------------------------------------------------------------------------
	EventPlayServerDisp() : Display Playbox:"ServerPlay"
----------------------------------------------------------------------------- */
function EventPlayServerDisp() {
	var objsmode = document.getElementById("idsmode");
	if (objsmode) {
		if(document.getElementById('idServer').checked) {		// Server PlayMode
			 objsmode.textContent="ServerPlay";
			 }
		else {
			 objsmode.textContent="";
			 }
		}
	}

/* ----------------------------------------------------------------------------
	EventStateRequest(mode) : UPnP Request Post:Playinfo
	mode     : i  : 0(省略時): BrowserからClick : PlayBoxを表示
					以外     : PostなどからのRequest (Block:block/noneは無視)
----------------------------------------------------------------------------- */
function EventStateRequest(mode) {
	if(mode==null) { mode=0; }
	if(mode==0) {
		EventBackoutTimer();			// BackoutTimerテスト
		}
	var objplaybox=document.getElementById('idPlayBox');
	var Render=document.getElementById('idSelRender').textContent;
	if(mode==0) {
		if (objplaybox.style.display=='block') {
			 _EventPBoxClose();
			 return;
			 }
		if(Render =="") {
			RequestMsgBox('UPnP:WebUI UPnPRequest','Select the Renderer and then Play.');
			return;
			}
		}
	if(mode==0) {									// CallbackなどのRequestはDisplay:block/noneを変更しない
		objplaybox.style.display='block';
		objplaybox=document.getElementById('idNowPlay').style.backgroundColor='#33cc99';
		}
	var modeName=document.getElementById('idSelmodelName').textContent;
	modeName=Render+"|"+modeName;					// modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
	var conturl=document.getElementById('idSelcontUrl').textContent;
	conturl=encodeURIComponent(conturl);
   console.log("EventStateRequest ; _EventUPnPLPlayinfo(ContURL["+conturl+']'+"]modeName["+modeName+"]):post nowplay");
	_EventUPnPLPlayinfo(conturl,modeName);
	RequestUPnPResize();
	}

/*
	_EventPBoxClose() : PlayBox Close
*/
function _EventPBoxClose() {
	var objplaybox=document.getElementById('idPlayBox');
	objplaybox.style.display='none';
	objplaybox=document.getElementById('idNowPlay').style.backgroundColor='';
	}

/*
	_EventUPnPLPlayinfo(conturl,modeName) : PlayBox(NowPlay):New & PlayList Postioning (再生Music最新&Playlistの位置)
	conturl  : i  : Renderer conturl URL
	modeName : i  : Render(FriendlyName)+"|"+modeName(modeName:機種名だけになってしまうので)
	※各Functionで.現在の再生Music&Playlistの位置をセットするために使用する
*/
function _EventUPnPLPlayinfo(conturl,modeName) {
// console.log("_EventUPnPLPlayinfo Start");
	RequestByPost(POSTMOD,'p=nowplay&conturl='+conturl+"&modename="+modeName,function(){ GetPLPlayPosition();RequestUnLoadDisp(); });
	}

/* ****************************************************************************
	PlayBox Function
	PlayBox Control
	 EventNowMusic()          : Event Loop (Control)
	 EventNowMusic_ReEnd()    : ReCheack,End (UPnPでは曲が終わると終了になるので End/NextPlayを判断する)
	 EventNowMusic_Next()     : Playlist NextPlay (再生時間経過時にNextPlayをGet)
	 EventNowMusic_Header()   : PlayPostion (Header表示用の項目をGet)
	 EventNowMusic_HeaderSet(): Item To PlayBox (各項目を使用し表示) (通常:EventNowMusic_Header→EventNowMusic_HeaderSet)
	 EventNowMusic_Restart()  : Playlist Timer Reset/Set . Restart (Timerを停止し再起動Event Loop)
	 GetPLPlayPosition()      : Get Play Position (現在のPlayPostionを得て表示、PlayPlayPostionをMark)
	※CommonFunctionは概要除く
***************************************************************************** */
var GTIMER_PLAY=1;				// Play Timer sec
var GTIMER_RENEW=30;			// Renew PlayTimer sec
var GTIMER_STATUS_SLEEP=30;		// Renew STOPTimer Sleep sec(Sleep刻み)
var GTIMER_STATUS=30;			// Renew STOPTimer sec : GTIMER_STATUS >= GTIMER_STATUS_SLEEP:となるように
var GTIMER_TRANS=1;				// Image "TRANSITIONING" Sleep sec(Sleep刻み)
<?php
global $G_IMGDURATION,$G_TRANSOF,$G_COMSEC;		// PHP:Setting Function:GetCommonConfig()
echo <<<EOM
// Controle PHP/JS Common
var GPTRANS_OF={$G_TRANSOF};	// "TRANSITIONING" Timeout(OverFlow) 
var GPLFASTTIME=10;				// Operation Fast Time (Fast FFW/REW) Sec
var GPCOMSEC={$G_COMSEC};		// 補完Time ServerがSTOPでClientはカウント継続しているケース。この値内であれば次を再生。
								// ※Play終了後の補完Time(Sec)。従い、設定時間が長いと,操作とEventの区別がつかなくなる。
// PHP/JS Common Literal
var G_IMGDURATION ="{$G_IMGDURATION}";		// Play,Image Duration

EOM;
?>

var GPTRANS=2;					// Image "TRANSITIONING" Get Status(Sec)
var GPRENEW_OF=GTIMER_RENEW;	// Renew CountUP(Statusにより変化)
// Counter
var GPLTime=0;					// Count, Play Time
var GPLRenew=0;					// Count, Renew:"PLAYING"中の最新Counter
var GPLCOMSEC=0;				// 補完Timeer sec
var GPLStat=0;					// Count,Status
var GPLTrans=0;					// "TRANSITIONING" Counter
var GPLTrans_OF=0;				// "TRANSITIONING" Timeout(OverFlow) Counter
// Status Control
var GCONTROL=0;					// 0:SetMode/1:TimerLoop
var GStatus="";
Timerid=false;

//EventNowMusic();	// Timer監視Start:RendererでKick:_EventRendererRowSet()に移行
/* ----------------------------------------------------------------------------
	EventNowMusic(loop) : IntervalTimer PlayMusic
	loop   : i  :  0:Reqest Timer Loop (2重Loop防止&停止防止)
	               1:Mode(タイマ解除して呼ぶこと"clearTimeout(Timerid)")
 ※Imageの場合 "TRANSITIONING"→"PLAYING" : 停止を発行しないとReldererは終了しない
   Music/Movie "PLAYING"→"STOPPED"
----------------------------------------------------------------------------- */
function EventNowMusic(loop) {
	var inttimer = GTIMER_PLAY;
	if (loop==null) { loop=0; }
	if ((loop==0)&&(GCONTROL==1)) { console.log("EventNowMusic Exit"); return; }			// Loop中チェック
	if (document.getElementById('idStatus')) {
		 GStatus=document.getElementById('idStatus').textContent;
		 }
	else {
		 return;
		 }
	GCONTROL=1;
	var imageflg="0";
	var objimageflg = document.getElementById("idImage");	// Image flag
	if (objimageflg) {
		imageflg=objimageflg.textContent;
		}
	if (GStatus.toUpperCase() === "PLAYING") {					// PlayTimer
		objTime=document.getElementById('idTime');
		if(objTime.textContent !=="") {
			GPLTime=ArgTimeToSeconds(objTime.textContent);
			}
		obDuration=document.getElementById('idDuration');
		var duration=ArgTimeToSeconds(obDuration.textContent);
		GPLCOMSEC=0;GPLStat=0;GPLTrans=0;GPLTrans_OF=0;
		if (SERVERPLAY =="on") {
			 var correct_time=GPLTime;							// ServerPlayはCallback分早いので補正しない(表示だけだし)
			 }
		else {
			 var correct_time=GPLTime+GTIMER_PLAY;				// SleepTimeを加えて補正(Event時Stopしているかも知れない)
			 }
		if (correct_time < duration) {
			 GPLTime=GPLTime+GTIMER_PLAY;
			 objTime.textContent=ArgSecondsToTime(GPLTime);		// Display:CorrentTime
			 if(imageflg=="0") {								// Imageは最新を取りに行かない
				 if (GPLRenew > GPRENEW_OF) {						// Renew Timer
					GPLRenew=0;										// 補完Timeer
					GPRENEW_OF=GTIMER_RENEW;						// RenewTimer Init.
console.log("EventNowMusic Renew Request idTime["+objTime.textContent+"] GPLTime["+GPLTime+"] duration["+duration+"]");
					EventStateRequest(1);							// Post:Playinfo
					}
				 GPLRenew=GPLRenew+GTIMER_PLAY;						// Renew監視Counter
				 remain=duration-GPLTime;							// remain,Play
				 if (remain > GTIMER_RENEW) {						// Play残が，GTIMER_PLAY以下のときは，Play残をTimerに設定
					  GPRENEW_OF=GTIMER_RENEW;
					  }
				 else {
					  GPRENEW_OF=remain;							// 終了予測でRenew (他Clientの操作には対応しない)
					  }
				 }
			 }
		else {
console.log("EventNowMusic Next Request HTML idTime["+objTime.textContent+"]GPLTime["+GPLTime+"] duration["+duration+"]");
			 objTime.textContent=obDuration.textContent;		// Next Music
			 if (EventNowMusic_Next(1,"on",imageflg) < 0) {		// ServerPlayMode時NextPlayしない
				EventStateRequest(1);							// Post:Playinfo
				}
			 }
		 }
	else if (GStatus.toUpperCase() === "TRANSITIONING"){		// "TRANSITIONING"(転送中)Status
		 GPLTrans_OF=GPLTrans_OF+GTIMER_TRANS;
		 if (GPLTrans_OF >= GPTRANS_OF) {						// "TRANSITIONING" TimeOut
			  GPLTrans_OF=0;
			  if (EventNowMusic_Next(1,"on",imageflg) < 0) { } else {	// OverFlow時,除いてNext(ServerPlayMode時NextPlayしない)
//			  if (EventNowMusic_Next(1,"on",imageflg) < 0) {	// OverFlow時,除いてNext(ServerPlayMode時NextPlayしない)
				  EventStateRequest(1);							// Post:Playinfo
				  }
			  }
		 else {
			  GPLTrans=GPLTrans+GTIMER_TRANS;
			  if (GPLTrans >= GPTRANS) {
				 GPLTrans=0;
				 EventStateRequest(1);							// Post:Playinfo
				 }
			  }
		 inttimer = GTIMER_TRANS;							// 転送中Timer
		 }
	else {
		 if(EventNowMusic_ReEnd(imageflg)) {					// Timerが不正確なので再チェック
			  }
		 else if(GPLStat >= GTIMER_STATUS) {					// Play以外の:Renew Timer
			  GPLStat=0;
			  EventStateRequest(1);									// Status Request
			  }
		 GPLStat=GPLStat+GTIMER_STATUS_SLEEP;					// Renew監視Counter
		 if (GPLCOMSEC <= GPCOMSEC) {							// 補完Time中は"PLAYING"のTimer
			 GPLCOMSEC = GPLCOMSEC+GTIMER_PLAY;
			 inttimer = GTIMER_PLAY;		// (明記的にセット:この命令がなくても同じTimer)
			 }
		else {
		 	 inttimer=GTIMER_STATUS_SLEEP;						// 補完Time中以降は STOPTimer
			 }
		 }
	var miritimer=inttimer*1000;
	Timerid=setTimeout(function () { EventNowMusic(1); },miritimer); // Timer Loop
	}

/* ----------------------------------------------------------------------------
	EventNowMusic_ReEnd(imageflg) : ReCheack,End,"PLAYING"が終了になったとき再チェック
	imageflg: i  : "1":imageの場合,StopはRendererがStopされた場合なので処理なし
	@return : o  : true : NextMusic Play Flase : End Muic
	※ PlayButtonがPlayのときNextチェックしてみる。PlayTimeと差が一定のときのみ行う。
----------------------------------------------------------------------------- */
function EventNowMusic_ReEnd(imageflg) {
	if(imageflg=="1") { return(false); }						// Imageのとき,stopは操作による中断。即時終了

	var rgbStr=document.getElementById('idCmdPlay').style.backgroundColor; //Play:"rgb(112, 128, 144)"→"#708090"
	var bgcolor=ArgRGBToHex(rgbStr);
	if(bgcolor=="#708090") {									// Buttonは、まだ"Play"状態
		obDuration=document.getElementById('idDuration');		// Timerが不正確なので"PLAYING"が終了時
		var duration=ArgTimeToSeconds(obDuration.textContent);	// ある秒数の場合 Nextチェックして見る
		GPLTime=ArgTimeToSeconds(objTime.textContent);
		if((duration-GPLTime) <= GPCOMSEC) {					// 補完Time中はTimerずれとしてNextCheack
			 if (EventNowMusic_Next(1,"on") < 0) { } else {
				 EventStateRequest(1,SERVERPLAY);				// Server Play Mode時NextPlayしない
				 return(true)
				 }
			 }
		}
	return(false);
	}

/*
	ArgRGBToHex(rgbStr) : HTML RGBToHex
	rgbStr  : i  : RGB Color Form:"rgb(112, 128, 144)"
	@return : o  : Hex Color Form:"#708090"
*/
function ArgRGBToHex(rgbStr) {
	const match = rgbStr.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);  // "rgb(r, g, b)" にマッチするか確認
	if (!match) {
	    return rgbStr;			    // 形式に合っていないときそのまま返す
		}
	const [r, g, b] = rgbStr.match(/\d+/g).map(Number);
	return "#" + [r, g, b]
		.map(x => x.toString(16).padStart(2, "0"))
		.join("");
	}

/* ----------------------------------------------------------------------------
	EventNowMusic_Next(vary,playmode,imageflg) : Playlist NextPlay
	vary    : i  : 現在のPositionからの移動Postion 省略時:+1(NextMusic)
	playmode: i  : Server Playmode:"on" :ServerModeに準じる
	imageflg: i  : "1":imageの場合Stopを発行
	@return : o  : 0:NormalEnd , -1:Playlistが無い場合，Listは全て終了時
	※RendererのImageFile表示は"Stop"画像モードを終了。SlideShowは最後に"Stop"
----------------------------------------------------------------------------- */
function EventNowMusic_Next(vary,playmode,imageflg) {
   console.log("EventNowMusic_Next Start");
	if (vary==null)     { vary=1; }
	if (playmode==null) { playmode=""; }
	if (imageflg==null) { imageflg=""; }
	var position=null;
	if (document.getElementById('idPlaypos')) {
		 var tmp=document.getElementById('idPlaypos').textContent;
		 if(tmp=="") { return(-1); }		// Play Positionがない
		 var str=tmp.split("Play Position : ");
		 var position=str[1];
		 }
	var objpltb=document.getElementById('idPlaylistTB');
	if ((objpltb)&&(position !== null)) {
		 var PLrow = objpltb.rows.length;
		 var pos=parseInt(position);
		 var i=((pos+vary)*2)-2;											// Next Positon
		 var cur=(pos*2)-1;													// Current Positon
		 if (i < 0) { return(-1); }
		 if (i > (PLrow -1)) {
			 if ((SERVERPLAY !=="on")&&(playmode =="on")&&(imageflg =="1")) {		// ServerPlay=off:無条件 & ServerPlay=on:&playmode =="off":ServerPlayに寄らない
				var objCmdStop=document.getElementById('idCmdStop');
				objCmdStop.onclick();		// StopButtonの関数を実行
				}
			 return(-1);
			 }
		 var str=document.getElementById('idNowURL').textContent;
		 if (str.indexOf(objpltb.rows[cur].cells[1].textContent)) { } else {
			 if (SERVERPLAY !=="on") {
				_RequestPBpx_TblClear();		// Playlistに見つからないので他からPlaylistが変更された判断
			 	RequestMsgBox('UPnP:WebUI UPnP Client:Playbox','Renderer playback information has changed(NextPlay)');
				}
			 document.getElementById('idPlaypos').textContent="";
			 return(-1);
			 }
		 if ((SERVERPLAY !=="on")||((SERVERPLAY =="on")&&(playmode !=="on"))) {		// ServerPlay=off:無条件 & ServerPlay=on:&playmode =="off":ServerPlayに寄らない
			  var targetDiv = objpltb.rows[i].cells[2].querySelector('div');		// <td>間の<div onclick="xx"を実行
			  targetDiv.onclick();		// Playlistの関数を実行
			  }
//console.log("EventNowMusic_Next : pos["+pos+"] return(0)");
//		 EventNowMusic_Header(pos+1);										// PlayList To PlayBox (Next)
		 else {
			  var wait=4000;
			  setTimeout(function () { EventNowMusic_Header(pos+1); },wait);	// PlayList To PlayBox (Next):本来はcallbackで実行が良いが
			  }
		 return(0);
		 }
console.log("EventNowMusic_Next : return(-1)");
	return(-1);
	}

/* ----------------------------------------------------------------------------
	EventNowMusic_Header(pos) : PlayPostion To Item
	pos    : i  : Play Postion
----------------------------------------------------------------------------- */
function EventNowMusic_Header(pos) {
	var i=(pos*2)-2;											// Next Positon
	var objpltb=document.getElementById('idPlaylistTB');
	var objcel = objpltb.rows[i].cells[0].querySelector('div');
	var rowTop = objpltb.rows[i];			// Uper(i)
	var rowBottom = objpltb.rows[i + 1];	// Under(i+1)
	var number = rowTop.cells[0].innerText.trim();						// UperCells
	var imgEl  = rowTop.cells[1].querySelector("img");
	var title  = rowTop.cells[2].innerText.trim();							// Title
	var track="";
	if (rowTop.cells[3]) {
		var lastCell = rowTop.cells[3];
		var hiddenDiv = lastCell.querySelector('div[style*="visibility:hidden"]');
		if (hiddenDiv) {
			track = hiddenDiv ? hiddenDiv.textContent.trim() : "";
			}
		}
	var imgPath="";
	if (imgEl) {
	    imgPath = imgEl.getAttribute("src");
		}
	var spans = rowBottom.cells[0].querySelectorAll("span");					// UnderCells
	var artist = spans[0].textContent.trim();									// Span:0 Artist
	var albumText = spans[1].innerHTML.trim();									// Span:1 Year"&nbsp;"Album
	var parts = albumText.split("&nbsp;");	// split : Year,Album
	var year  = parts[0];														// Year
	var album = parts.slice(1).join(" ");										// Album
	    album = album.split("</span>")[0];
	album=ArgHtmlDecode(album);
    var trackPath   = rowBottom.cells[1].innerText.trim();						// TrackURL
	var cell = rowBottom.cells[2];             							    	// duration;3Cells,visibility:hidden
	var divs = cell.getElementsByTagName("div");								// 
	var duration = divs[1].textContent.trim(); 									// div2個め "H:MM:SS"
	var progress="0:00:00";														// Time(経過)
	var imageflg ="0";
	if(divs[2]) {
		imageflg = divs[2].textContent.trim(); 									// div3個め Imageflg="1":画像
		if(imageflg=="1") {
			duration = G_IMGDURATION;											// Play,Image Duration (PHPSetting)
			}
		}
//	console.log("number["+number+"] imgPath["+imgPath+"]title"+title+"]artist["+artist+"]album+["+album+"]duration["+duration+"]track["+track+"]");
	var item = {
		number: number,
		image: imgPath,
		title: title,
		track: track,
		artist:artist,
		year:year,
		album: album,
		duration:duration,
		progress:progress,
		trackPath: trackPath,
		imageflg: imageflg
		};
	EventNowMusic_HeaderSet(item);
	GPLTime=0;
	}

/*
	ArgHtmlDecode(str) : HTML Entity (innerHTML →PlainText)
	str    : i  : String (innerHTML)
	@return: o  : Decode String
*/
function ArgHtmlDecode(str) {
	var txt = document.createElement("textarea");
	txt.innerHTML = str;
	return txt.value;
	}

/*
	EventNowMusic_HeaderSet(item): Item To PlayBox
	item   : i  : item,Structure Object
	              item : number,image,title,track,artist,year,album,trackPath: trackPath,imageflg: imageflg
*/
function EventNowMusic_HeaderSet(item) {
	var img = document.querySelector("#idPlaydtl img");		// Art Image
	var sty = document.querySelector("#idPlaydtl .playboxdiv");
	if ((img) && (item.image !=="")) {
		 img.src = item.image;
		 img.style.display = "block";
		 sty.display = "none";
		 }
	else {
		 img.style.display = "none";
		 sty.style.display = "block";
		 }
	var artist = document.querySelectorAll("#idPlaydtl .nowplayhd")[0];		// Artist
    if (artist) {
		artist.textContent = item.artist;
		}
    var album = document.querySelectorAll("#idPlaydtl .nowplayhd")[1];		// Album
	if (album) {
		album.textContent = item.album;
		}
    var title = document.querySelectorAll("#idPlaydtl .nowplayhd")[2];		// Title
	if (title) {
		title.textContent = item.title;
		}
    var track = document.querySelector("#idPlaydtl .nowplayhds");			// Track.No
	if (track) {
		if(item.track !=="") {
			 track.textContent = "Track:" + item.track;						// originalTrackNumber
			 }
		else {
			 track.textContent = "";
			 }
		}
	var duration = document.getElementById("idDuration");					// duration
	if (duration) {
		duration.textContent = item.duration;
		}
	var progress = document.getElementById("idTime");						// Time)
	if (progress) {
		progress.textContent = item.progress;
		}
	var imageflg = document.getElementById("idImage");						// Image flag
	if (imageflg) {
		imageflg.textContent = item.imageflg
		}
	var playpos = document.getElementById("idPlaypos");						// Play Position
	if (playpos) {
		if (item.number > 0) {
			 playpos.textContent = "Play Position : " + item.number;
			 GetPLPlayListPos(playpos.textContent);
			 }
		else {
			 playpos.textContent = "";
			 }
		}
	}

/* ----------------------------------------------------------------------------
	EventNowMusic_Restart() : Playlist Timer Reset/Set . Restart
	Stop->Start時Timerを解除し，SleepTimeを変更
----------------------------------------------------------------------------- */
function EventNowMusic_Restart() {
	if (Timerid) {
		clearTimeout(Timerid);
		Timerid=false;
		}
	GCONTROL=0;
	EventNowMusic();
	}

/* ----------------------------------------------------------------------------
	GetPLPlayPosition() : Get Play Position
----------------------------------------------------------------------------- */
function GetPLPlayPosition() {
	var position="";
   console.log("GetPLPlayPosition Start");
	RequestPBxStatus();									// Status Coler
	var objurl=document.getElementById('idNowURL');		// NowPlay URL
	if (objurl && objurl.textContent.trim() !== "") {
		url = new URL(objurl.textContent);
		var path=url.pathname;
		var objpltb=document.getElementById('idPlaylistTB');
		if (objpltb) {
			var PLrow = objpltb.rows.length;
			if (PLrow > 0) {
				for(var i=1; i < PLrow;i=i+2) {
					var url_track = objpltb.rows[i].cells[1].textContent;
					if(url_track==path) {
						position= "Play Position : "+(parseInt(i/2)+1);
						}
					}
				}
			}
		}
	if (document.getElementById('idPlaypos')) {
		var removepos=document.getElementById('idPlaypos').textContent;
		document.getElementById('idPlaypos').textContent=position;
		if(position !=="") {
			 GetPLPlayListPos(position);
			 }
		else if((objpltb)&&(objurl.textContent !=="")) {
			 if (SERVERPLAY !=="on") {
				_RequestPBpx_TblClear();					// Playlistに見つからないので他からPlaylistが変更された判断
				RequestMsgBox('UPnP:WebUI UPnP Client:Playbox','Renderer playback information has changed');
				}
			 }
		}
   console.log("GetPLPlayPosition position ["+position+"]["+path+"] -> EventNowMusic");
	EventNowMusic();		// Timer監視Start(Loop時無視)	// PlayPosition後にIntervalTimer
	}

/*
	GetPLPlayListPos(pos) : PlayList BackColor Set/Reset
	pos   : i  : Set Postion (From , 'idPlaypos':"Play Position : {Pos}")
*/
function GetPLPlayListPos(pos) {
	var objpltb=document.getElementById('idPlaylistTB');
	if (objpltb) {
		var str=pos.split("Play Position : ");
		var intpos=str[1];
		var setpos=(intpos-1)*2;
		for (var i=0;i< objpltb.rows.length;i=i+2) {
			if(i==setpos) {
				 var bcolor='#26004d';
				 }
			else {
				 var bcolor='';
				 }
			objpltb.rows[i].cells[0].style.backgroundColor = bcolor;
			objpltb.rows[i].cells[2].style.backgroundColor = bcolor;
			objpltb.rows[i+1].cells[0].style.backgroundColor = bcolor;
			objpltb.rows[i+1].cells[1].style.backgroundColor = bcolor;
			objpltb.rows[i+1].cells[2].style.backgroundColor = bcolor;
			}
		}
	}

/* ----------------------------------------------------------------------------
	ArgTimeToSeconds(timeString) : Time To String:Second
	timeString : i  : hh:mm:ssの形式
	@return    : i  : Second
----------------------------------------------------------------------------- */
function ArgTimeToSeconds(timeString) {
	const [hours, minutes, seconds] = timeString.split(':').map(Number); // 時・分・秒を分解して数値に変換
	return hours * 3600 + minutes * 60 + seconds; // 秒に変換して合計を返す
	}

/*
	ArgSecondsToTime(seconds) :Second To  Time To String:
	seconds    : i  : Second
	@return    : i  : hh:mm:ssの形式
*/
function ArgSecondsToTime(seconds) {
	const hours = Math.floor(seconds / 3600); // 時間を計算
	const minutes = Math.floor((seconds % 3600) / 60); // 分を計算
	const secs = seconds % 60; // 残りの秒を計算
    return `${String(hours).padStart(1, '0')}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
	}

/* ****************************************************************************
	PlayBox Command Function

 ※動作の仕組み
 1.ServerPlay:ServerProcessがNextPlay,Statusを監視している。
   ServerProcessが動作中は、Clientからの操作はServerProcessはRendererのStatusから知る。
 2.ServerProcessは一定時間,PAUSE/STOPPEDを検知するとProcessを終了する。PAUSE時はSTOPPEDに遷移させる。
   statusがSTOPPEDのときはServerProcessはないこととしてCommandを実行する。このときはPLAYING状態にする動作のみ有効
   ServerProcessがない状態にするため,QUEUE:"kill"を発行のProcess間通信を行う。
   (ArchLinuxなどProcess間通信Libraryがない場合は無視するが、状態の不一致となるので手操作で停止/Clearを行う前提)
 ※ServerPlay時,ServerProcessはCliantからのPostで起動する。
   現在、ServerPlayのResourceを無用に増やさないことの考慮から。
   安定を確認できたら、Rendererごとにkickし操作が一定時間なければ消滅するようにすることがBestと考えている。
***************************************************************************** */
<?php
global $G_SYSVMSG;
echo <<<EOM
var GSYSVMSG= "{$G_SYSVMSG}";	// GSYSVMSG: sysvmsg : Used,System V Message Queue , "" : Not Used
EOM;
?>

/* ----------------------------------------------------------------------------
	RequestPBoxPlay(cmd) : Request PlayBox Play
	cmd        : i  : PlayBox Command Clear/ErrorClear RSkip/FSkip FFW/REW Play/Pause/Stop
----------------------------------------------------------------------------- */
function RequestPBoxPlay(cmd) {
	var addcmd="";var stsstr="";
	var Render=document.getElementById('idSelRender').textContent;
	if(Render =="") {
		RequestMsgBox('UPnP:WebUI UPnPRequest','Select the Renderer and then Play.');
		return;
		}
	if (document.getElementById('idStatus')) {				// Pauseのとき,StopTimerを解除後のLoopを補助
		GStatus=document.getElementById('idStatus').textContent;
		stsstr=GStatus.toUpperCase();
		}
	var objurl=document.getElementById('idNowURL');
	if ((objurl == null)&&(cmd !=="Stop")&&(cmd !=="Clear")&&(cmd !=="ErrorClear")) {		// Stopは別端末などより無条件で発行
		 if(cmd =="Play") {
			if(RequestPBoxPlayCmd()) { return; }											// 状態がSTOPEDで"Play"Buttonで先頭をPlay
			}
		 RequestMsgBox('UPnP:WebUI RendererRequest','There is no Playback Content.');
		 return;
		 }
	if (cmd =="Clear") {									// CMD:Clear Client;TBLClear
		_RequestPBpx_TblClear();
		}
	if ((cmd=="RSkip")||(cmd=="FSkip")) {					// Beffer/Nextは，MusicPostionをClick
		var vary =1;
		if(cmd=="RSkip") { vary =-1; }
		if(stsstr.includes('PAUSED')){
			document.getElementById('idStatus').textContent="PLAYING";
			}
		EventNowMusic_Next(vary);
		return;
		}
	if ((cmd=="FFW")||(cmd=="REW")) {						// Beffer/Nextは，MusicPostionをClick
		var fasttime=_RequestPBpx_fastplay(cmd);
		if(fasttime < 0) { return; }
		addcmd="&fasttime="+fasttime;
		}
	var conturl=document.getElementById('idSelcontUrl').textContent;
	conturl=encodeURIComponent(conturl);
	var modeName=document.getElementById('idSelmodelName').textContent;
	modeName=Render+"|"+modeName;							// modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
	if (objurl == null) {
		 var url="null";
		 }
	else {
		 var url=objurl.textContent;
		 }
	url=encodeURIComponent(url);
	if ((cmd=="Play") ||(cmd=="Pause")) {					// Playに変わったときTimer Restart
		if (document.getElementById('idStatus')) {				// Pauseのとき,StopTimerを解除後のLoopを補助
			GStatus=document.getElementById('idStatus').textContent;
			stsstr=GStatus.toUpperCase();
			if (stsstr.includes('PAUSED')){
				 document.getElementById('idStatus').textContent="PLAYING";
				 }
			}
		var position="";var i=-1;								// ServerPlay時PauseでProcess停止状態の可能性があるので現在のPositionを求める
		if (document.getElementById('idPlaypos')) {
			var tmp=document.getElementById('idPlaypos').textContent;
				if(tmp !=="") {
				var str=tmp.split("Play Position : ");
				position=str[1];
				var pos=parseInt(position);
				i=(pos*2)-2;
				}
			 }
		if ((SERVERPLAY =="on")&&(cmd=="Play")&&(i !==-1)&&(stsstr.includes('PAUSED'))) {	// ServerPlay時Positionを実行する。(ServerPlayはPAUSE:TimeoutでSTOPPEDにする)
			 var objpltb=document.getElementById('idPlaylistTB');
			 var targetDiv = objpltb.rows[i].cells[2].querySelector('div');	// <td>間の<div onclick="xx"を実行
			 targetDiv.onclick();				// Playlistの関数を実行
			 }
		else {
			 RequestByPost(POSTMOD,'p=playpanel&cmd='+cmd+addcmd+'&conturl='+conturl+'&url='+url+"&modename="+modeName,function(){ EventStateRequest(1);EventNowMusic_Restart(); });
			 }
		 }
	else if ((SERVERPLAY =="on")&&(cmd=="Stop")) {
		 RequestLoadDisp();
		 if (GSYSVMSG !=="") {					// GSYSVMSG: Used,System V Message Queue
			  RequestByPost(POSTMOD,'p=playpanel&cmd='+cmd+addcmd+'&conturl='+conturl+'&url='+url+"&modename="+modeName+"&serverplay="+"on",function(){ _EventPBoxClose();RequestUnLoadDisp(); });
			  }
		 else {
			  RequestByPost(POSTMOD,'p=playpanel&cmd='+cmd+addcmd+'&conturl='+conturl+'&url='+url+"&modename="+modeName+"&serverplay="+"on",function(){ _EventPBoxClose();_RequestPBpx_TblClear();RequestUnLoadDisp(); });
			  }
		 }
	else {
		 RequestByPost(POSTMOD,'p=playpanel&cmd='+cmd+addcmd+'&conturl='+conturl+'&url='+url+"&modename="+modeName,function(){ EventStateRequest(1); });
		 }
	}

/*
 	RequestPBoxPlayCmd() : Command: Play (STOPPEDのPlay)
	@return : o  : True:Play実行,False:Play未実行
	※PlayはPauseに対してだが,Pauseがないとき先頭からPlay
*/
function RequestPBoxPlayCmd() {
	var objpltb=document.getElementById('idPlaylistTB');
	if ((objpltb)&&(objpltb.rows[0])) {
		document.getElementById('idStatus').textContent="PLAYING";
		var targetDiv = objpltb.rows[0].cells[2].querySelector('div');	// <td>間の<div onclick="xx"を実行
		targetDiv.onclick();		// Playlistの関数を実行
		return(true);
		}
	return(false);
	}

/* ----------------------------------------------------------------------------
	EventPBFFTimer(cmd) : Event Request PlayBox Fast F/R Timer
	cmd        : i  : PlayBox Command FFW/REW 
----------------------------------------------------------------------------- */
function EventPBFFTimer(cmd) {
	var ffsleep=3000;
	RequestPBoxPlay(cmd);
	GFFTimer=setTimeout( function() { EventPBFFTimer(cmd); },ffsleep);
	}

/* 
	EventPBFFReset() : Event Request PlayBox Fast F/R Timer Reset
 */
function EventPBFFReset() {
	clearTimeout(GFFTimer);
	}

/*
	RequestPBxStatus() : Request PlayBox Play Status
*/
function RequestPBxStatus() {
	var obStatus=document.getElementById('idStatus');
	if (obStatus) {
		if(obStatus.textContent=="PLAYING") {
			 document.getElementById('idCmdPlay').style.backgroundColor="#708090";
			 document.getElementById('idCmdStop').style.backgroundColor="";
			 document.getElementById('idCmdPause').style.backgroundColor="";
			 }
		else if(obStatus.textContent=="STOPPED") {
			 document.getElementById('idCmdPlay').style.backgroundColor="";
			 document.getElementById('idCmdStop').style.backgroundColor="#708090";
			 document.getElementById('idCmdPause').style.backgroundColor="";
			 }
		else if(obStatus.textContent=="PAUSED_PLAYBACK") {
			 document.getElementById('idCmdPlay').style.backgroundColor="";
			 document.getElementById('idCmdStop').style.backgroundColor="";
			 document.getElementById('idCmdPause').style.backgroundColor="#708090";
			 }
		}
	}

/* ----------------------------------------------------------------------------
	_RequestPBpx_fastplay(cmd) : "Fast FFW" , "Fast REW" Time
	cmd    : i  : "FFW"||"REW"
----------------------------------------------------------------------------- */
function _RequestPBpx_fastplay(cmd) {
	if (document.getElementById('idStatus')) {
		GStatus=document.getElementById('idStatus').textContent;
		if (GStatus.toUpperCase() != "PLAYING") { return(-1); } // Play Only
		objTime=document.getElementById('idTime');
		pltime=ArgTimeToSeconds(objTime.textContent);
		if(cmd=="FFW") {
			 pltime=pltime+GPLFASTTIME
			 obDuration=document.getElementById('idDuration');
			 if(pltime >= obDuration) {							// FastがNowMusic Over
				EventNowMusic_Next(1);
				return(-1);
				}
			 var duration=ArgTimeToSeconds(obDuration.textContent);
			 }
		else {
			 pltime=pltime-GPLFASTTIME;
			 if(pltime <= 0) {									// FastがNowMusic Under
				EventNowMusic_Next(-1);							// 今のところ前の曲の先頭
				return(-1);
				}
			 }
		var fastime=ArgSecondsToTime(pltime);
		return(fastime);
		}
	return(-1);
	}

/*
	_RequestPBpx_TblClear(): PB Playlist Table Clear(remove)
*/
function _RequestPBpx_TblClear() {
	var objpbtbl=document.getElementById('idPlaylistTB');
	if (objpbtbl) {
		document.getElementById("idPlaylistTB").remove();
		}
	var objpbttl=document.getElementById('idPlaylistTtl');
	if (objpbttl) {
		document.getElementById("idPlaylistTtl").remove();
		}
	}

/* ----------------------------------------------------------------------------
	RequestUPnPContst(url) : UPnPContst Action
	url   : i  : ssdpUrl (Home)
----------------------------------------------------------------------------- */
function RequestUPnPContst(url) {
	var objGetUPnp=document.getElementById('idGetUPnP');
	objGetUPnp.textContent="UPnP";
	objGetUPnp.style.backgroundColor='';
	objGetUPnp.onclick = function(){ EventUPnPGet(2); };

	document.getElementById('idServereptitle').textContent="ServerPlay";
	document.getElementById('idServer').style.display='block';

	if (GSERVERPLAY=="on") {							// ServerPlay Default
		document.getElementById('idServer').checked = true;
		}
	var objParent=document.getElementById('idParent');
	objParent.style.display='block';
	objParent.onclick = function(){ EventUPnPDevices(url); };
	}

/* ----------------------------------------------------------------------------
	RequestUPnPReset() : UPnPContst Action Reset
----------------------------------------------------------------------------- */
function RequestUPnPReset() {
	document.getElementById('idServName').textContent="MediaServer";

	var objGetUPnp=document.getElementById('idGetUPnP');
	objGetUPnp.textContent="Get UPnP";
	objGetUPnp.style.backgroundColor='#33cc99';
	objGetUPnp.onclick = function(){ EventUPnPGet(2); };

	document.getElementById('idServereptitle').textContent="";
	document.getElementById('idServer').style.display='none';

	var objParent=document.getElementById('idParent');
	objParent.style.display='none';
   ArgOptMiniDLNAReSet();					//拡張Function Reset
	RequestUPnPResize();
	}

/* ****************************************************************************
	Common Library
***************************************************************************** */
/* ----------------------------------------------------------------------------
	RequestMsgBox(title,str,top,left) : MessageBox (mpdweb標準モジュールにStringLength追加)
	 title: i  : Box Title
	 str  : i  : Message
	 top  : i  : Display Top
	 left : i  : Display Left
----------------------------------------------------------------------------- */
function RequestMsgBox(title,str,top,left) {
	str=str.replace(/<BR>/g, "\n");
	var ary=str.split("\n");
	var leng=0;
	for (i = 0; i < ary.length; i++) {
		if (ary[i].length > leng) {
			leng=ary[i].length;
			}
		}
	if (leng > 50){
		 document.getElementById('idMsgBoxstr').style.fontSize = "9px";
		 document.getElementById('idMsgBoxstr').style.top = "36px";
		 document.getElementById('idMsgBoxstr').style.lineHeight = "130%";
		 }
	else {
		 document.getElementById('idMsgBoxstr').style.fontSize = "13px";
		 document.getElementById('idMsgBoxstr').style.top = "60px";
		 document.getElementById('idMsgBoxstr').style.lineHeight = "20px";
		 }
	if (title == null) { title="Message Box"; }
	if (top == null) { top=160; }
	document.getElementById('idMsgBox').style.display='block';
	document.getElementById('idMsgBox').style.top=top;
	if (left !== null) {
		document.getElementById('idMsgBox').style.left=left;
		}
	document.getElementById('idMsgBoxtitle').textContent=title;
	document.getElementById('idMsgBoxstr').textContent=str;
	}

/* ----------------------------------------------------------------------------
	RequestLoadDisp(waitsec) : 起動待ちの時計マークディスプレイ (mpdweb標準モジュールより)
	 waitsec : i  : Display Timeout(Sec) 0:Limit(Net Timeoutまで),null:defult,{n}:指定時{n}sec
------------------------------------------------------------------------------ */
var LoadDispWSec=30;
var GObjSTime;				// 起動待ちの時計マーク Objectid
function RequestLoadDisp(waitsec){
	var sleep=null;

	if (waitsec == 0) {
		 sleep=LoadDispWSec*1000;
		 }
	else if (waitsec == null) {
		 sleep=10000;
		 }
	else {
		 sleep=waitsec*1000;
		 }
	document.getElementById('idlodingmsg').style.display='block';	// idlodingmsgを表示にする display: none;→block

	var element = document.createElement('div'); 					// ロック用のdivを生成 透明，最大画面を最上位に表示
	element.id = "screenLock"; 										// ロック用のスタイル
	element.style.height = '100%'; 
	element.style.left = '0px'; 
	element.style.position = 'fixed';
	element.style.top = '0px';
	element.style.width = '100%';
	element.style.zIndex = '100';									// PlayPanel(id=idFormCtl)よりz-indexを下げている
	element.style.opacity = '0';
	var objBody = document.getElementsByTagName("body").item(0); 
	objBody.appendChild(element);

	if(GObjSTime) { clearTimeout(GObjSTime);GObjSTime=null; }							// Timer Unloak
	GObjSTime=setTimeout( function() { RequestUnLoadDisp(); },sleep);	// ロック画面の削除
	}

/*
	RequestUnLoadDisp() 起動待ちの時計マーク解除
*/
function RequestUnLoadDisp(){
	document.getElementById('idlodingmsg').style.display='none';
	var id_name='screenLock';
	if (document.getElementById(id_name) == null) {
		return;
		}
	if(GObjSTime) { clearTimeout(GObjSTime);GObjSTime=null; }							// Timer Unloak
    var dom_obj = document.getElementById(id_name);
    var dom_obj_parent=dom_obj.parentNode;
    dom_obj_parent.removeChild(dom_obj);
	}

/* Child Close Button */
function EventSelfClose() {
//	alert(phost);
	if (window.parent && typeof window.parent.mpdWatchSet === "function") {		// WatchStatus()のSet/Reset Frameなどでreloadさせないを解除
		window.parent.mpdWatchSet(1);	// 
		}
	if(phost !=="") {					// 別Hostから起動時postする。
		window.parent.postMessage("close",phost);
		return;
		}
	var pobj = window.frameElement;		// 自身を参照している iframe 要素を取得
	if (pobj) {
		pobj.parentNode.style.pointerEvents = 'auto';	// 親要素のイベントを再有効化
		pobj.remove();					// 自身の iframe を削除
		}
	window.close();
	}

<?php
/* ----------------------------------------------------------------------------
	Use JS PHP:"$MPC_MONITOR"設定 : Monitorの初期値 & EventNowMusic TimerSet
----------------------------------------------------------------------------- */
$tmp=$_SERVER['HTTP_REFERER'];
$ary = parse_url($tmp);
$myhost = explode(":",$_SERVER['HTTP_HOST'])[0];
$phost="";
if ((isset($ary['host']))&&($ary['host'] !== $myhost)) {		// 他Serverのときは，終了時Postする。
	if (isset($ary['port'])) { $port=":".$ary['port']; }
	$phost=$ary['scheme']."://".$ary['host'].$port;
	}
echo <<<EOM
var MPC_MONITOR="{$MPC_MONITOR}";
var phost="{$phost}";
EOM;
?>

/* -----------------------------------------------------------------------------
	Event Window MediaServer,Resize
----------------------------------------------------------------------------- */
window.addEventListener('resize',function(){ RequestUPnPResize(); });

function RequestUPnPResize(){
	if ( document.getElementById('idUPnPRender')) {
		 document.getElementById('idUPnPtable').style.height=window.innerHeight - HEIGHT_DEF;
		 }
	else if (document.getElementById('idDetaile')) {
		 document.getElementById('idDetaile').style.height=window.innerHeight - 76;
		 }
	if (document.getElementById('idPlaylistTB')) {
		var PLrow = document.getElementById('idPlaylistTB').rows.length;
		if(PLrow > 0) {
			document.getElementById('idPlaylist').style.height=window.innerHeight - 206-50;	// PB-Height:206, PBPlayListBar:18+Def:32
			}
		}
	}

/* -----------------------------------------------------------------------------
	Event OnLoad (DOMContentLoaded検出でOnLoad)
----------------------------------------------------------------------------- */
document.addEventListener('DOMContentLoaded', () => {
	RequestUPnPResize();
	document.getElementById('idGetUPnP').style.backgroundColor='#33cc99';
	});

/* ****************************************************************************
	WakeLock Timer (Test中)
***************************************************************************** */
let wakeLock = null;
let wakeLockTimer = null;
async function EventBackoutTimer() {
	if ('wakeLock' in navigator && !wakeLock) {			    // wakeLock Timer
		try {
			wakeLock = await navigator.wakeLock.request('screen');
			console.log('WakeLock acquired');
			} catch (err) {
			console.error(`WakeLock failed: ${err.name}`);
			}
		}
	if (!wakeLockTimer) {								    // Playlist タイマー
		wakeLockTimer = setInterval(() => {
			requestNextTrack(); 							// サーバ問い合わせや次曲再生
			}, 30000); 										// 30秒ごと
		console.log('WakeLock timer started');
		}
	}

</script>

<?php
/* ****************************************************************************
	UPnP Main Process
	Display Function
	 Get_ConfigGet()  :Common Item Config
	 DispUPnP()       :Display UPnP Main
	 DispUPnPDetaile():Display UPnP Detaile
	 DispUPnPHeader() :Display UPnP Detaile
	               JSP:Close SubWindow
	               JSP:拡張Function Global
	 DispUPnPStatus() :Display Status()
	 DispMsgBox()     :Display MessageBox定義 
	※SubFunction除く
***************************************************************************** */
DispUPnP($G_RENDERHEIGHT);

/* -----------------------------------------------------------------------------
	Get_ConfigGet() Get CommonConfig
----------------------------------------------------------------------------- */
function GetCommonConfig() {
	global $G_RENDERHEIGHT,$G_RENDERHEIGHT_DEF,$G_MAKESSDP,$G_CASTCONF,$G_BROWSEMAX,$G_PLAYMAX,$G_EXCLUDEXT,$G_IMAGEEXT,$G_SERVERPLAY;

	$G_RENDERHEIGHT=310;							// UPnP Renderer部Height Size , Androidは，MediaServer部のHeight Sizeも必要なので$G_RENDERHEIGHT_DEFを設定
	$G_RENDERHEIGHT_DEF=$G_RENDERHEIGHT+54;			// UPnP MediaServer以外Height - Mediaserver部Herader(MediaserverHeight= Window Size - $G_RENDERHEIGHT_DEF)

	$makessdp="on";									// SSDPCastのfile[config.txt]を使用するか設定:"on":使用する。
	$G_MAKESSDP = $_POST['makessdp'] ?? $_GET['makessdp'] ?? $makessdp;
	$G_MAKESSDP=strtolower($G_MAKESSDP);
	if($G_MAKESSDP !=="off") {  $G_MAKESSDP=$makessdp; }

	if ($G_MAKESSDP=="on") {
		$dirpath = dirname(__FILE__). '/setting';
		if(file_exists($dirpath)){						// ./settingがあれば使用する
			 $G_CASTCONF = $dirpath.'/'.basename(__FILE__, ".php")."_config.txt";				// Multicast /setting'./{FileName(自己Filename.txt)}
			 }
		else {
			 $G_CASTCONF = dirname(__FILE__).'/'.basename(__FILE__, ".php")."_config.txt";		// Multicast ,.{FileName(自己Filename.txt)}
			 }
		}
	$G_PHPTIMEOUT=180;									// PHPのScriptが実行可能秒数
	set_time_limit($G_PHPTIMEOUT);
	$G_BROWSEMAX=2000;									// Content Max
	$G_PLAYMAX=300;										// Content Play Max

	$G_EXCLUDEXT=array("jpg", "bmp", "png", "txt");		// Play,Excluded extensions
	$G_IMAGEEXT =array("jpg", "bmp", "png");			// Alter Icon, Image extensions

	$G_SERVERPLAY ="on";								// ServerPlay Default

	global $G_TIMEOUT_SSDPREQ,$G_TIMEOUT_BROWSE,$G_TIMEOUT_CONTENTS,$G_TIMEOUT_RENDERER,$G_SUBWINTOP,$G_TIMEOUT_RENDERERV,$G_SYSVMSG;	// Sock Timeout
	$G_TIMEOUT_SSDPREQ=1.6;								// Soket Timeout SSDP Request(getDeviceSocket)
	$G_TIMEOUT_BROWSE=3;								// Soket Timeout BrowseContentSock(browseContentSock)
	$G_TIMEOUT_CONTENTS=180;							// Soket Timeout Contents(browseContentSock)
	$G_TIMEOUT_RENDERER=3;								// Soket Timeout RenderSockOpen(sendSoapSock)
	$G_TIMEOUT_RENDERERV=8;								// Soket Timeout ReRenderSock Receive Timeout(sendSoapSock)

	global $G_IMGDURATION,$G_TRANSOF,$G_COMSEC;
	$G_IMGDURATION="0:00:10";							// Play,Image Duration
	$G_TRANSOF=60;										// "TRANSITIONING" Timeout(OverFlow) 
	$G_COMSEC=10;										// 再生時間の誤差マージンこの値を誤差として継続再生と見みなす。(再生時間と再生終了:STOPEDの誤差)

	$G_SYSVMSG = system('php -m | grep sysvmsg', $retval);
	$G_SYSVMSG =trim(rtrim($G_SYSVMSG));				// $G_SYSVMSG : sysvmsg : Used,System V Message Queue , "" : Not Used

	global $G_MPDCLI,$G_MPDCLICONF,$G_MPDSET,$G_MPDCONF;	// 外部モジュール
	$G_MPDCLI    ="./mpdcliconf.php";					// Edit:upmpdcli.conf
	$G_MPDCLICONF ="/etc/upmpdcli.conf";
	$G_MPDSET    ="./mpdconfig.php";					// Edit:mpd.conf ALSA Device
	$G_MPDCONF    ="/etc/mpd.conf";
	$G_SUBWINTOP="110";


	global $G_EXFUNC;	// <<拡張Function>>
	$G_EXFUNCITEM=array("minidlna"   =>"UP;|88;mpdweb|;mpdminidlnadb.php"	// minidlna 	// 外部設定に移行を考慮
				  ,"MinimServer"=>"UI;9790");								// MinimServer
	$G_EXFUNC=array();
	foreach ($G_EXFUNCITEM as $key => $item) {
		$it  =explode(";",$item);				// Itemを項目に分解
		if(!isset($it[1])) { continue; }		// 比較対象がないので設定なしとする
		$G_EXFUNC[$key]["btn"]="UP";$G_EXFUNC[$key]["port"]="";$G_EXFUNC[$key]["path"]="";$G_EXFUNC[$key]["mod"]="";
		if($it[0] !=="")  { $G_EXFUNC[$key]["btn"] =$it[0]; }
		$G_EXFUNC[$key]["port"]=$it[1];
		if(isset($it[2])) { $G_EXFUNC[$key]["path"]=$it[2]; }
		if(isset($it[3])) { $G_EXFUNC[$key]["mod"] =$it[3]; }
		}
	}

/* ----------------------------------------------------------------------------
	DispUPnP() : Display UPnP
----------------------------------------------------------------------------- */
function DispUPnP() {
	global $G_RENDERHEIGHT,$G_RENDERHEIGHT_DEF,$SOCK_ERRORMSG,$G_MAKESSDP;

	$Content=array();		// Content  Array:["location"]/["deviceType"]/["deviceType"]/["services"]:array
	$Renderer=array();		// Renderer Array:["location"]/["deviceType"]/["deviceType"]/["services"]:array
echo <<<EOM
<body style="background-color:#000000;color:#ffffff;">
<style type="text/css">
/* ScrollBar */
.scrbar { overflow-y: scroll;overflow-x: hidden; }			/* 常にスクロールバーを表示 */
.scrbar::-webkit-scrollbar { height:8px;width: 8px;} 		/* スクロールバー:トラック部分 スクロールバーの幅 */
.scrbar::-webkit-scrollbar-track { background: #1c1c1c; box-shadow: inset 0 0 1px #2e2e2e; }	/* スクロールバー背景 */
.scrbar::-webkit-scrollbar-thumb { background: #2f4f4f; }	/* スクロールバーつまみ */
/* DispUPnPDetaile */
.head  { position:fixed !important;position:absolute;top:0;width:484px;height:25px;font-size:13px;background-color:#000000;color:#ffffff;z-index:400; }
.headline { top:0px;height:20px;background-color:#333333;width:475px;border:0.6px solid #606060;padding:5px 0 4px 6px;}
.headTi { position:absolute;top:7px;height:16px;background-color:#1c1c1c;color:#e8e8e8;font-size:12px;text-align:center;padding: 2px 1px 2px 0px;margin: -2px 0 0 -3px; }
.headtx{ position:absolute;top:5px;left:94px;height:15px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:break-all;background-color:#1c1c1c;border:1.2px groove #090909;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;padding:0px 0 4px 6px; }
.headhd{ position:absolute;top:0px;font-size:9px;visibility:hidden; }
.headsub { top:20px;font-size:12px;color:#ffffff;background-color:#003366;width:475px;height:16px;padding:4px 0 4px 6px;border:1px solid #606060; }
.headsbt { top:20px;font-size:12px;color:#ffffff;background-color:#003366;font-size:9px }
.headrendr { font-size:12px;background-color:#003366;color:#ffffff;width:475px;height:17px;padding:6px 0 4px 6px;border:1px solid #606060; }
.headfun { position:fixed;top:6px;left:459px;width:20px;height:18px;white-space:normal;background-color:#008000;color:#CED8F6;border-style:none;font-size:8p;cursor:pointer;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;text-align:center;transform:rotate(-90deg);padding:0px 0px 4px 0px; }
.headCls { position:fixed;top:5px;left:468px;width:20px;height:21px;white-space:normal;background-color:#c0c0c0;color:#2c2c2c;border:1px groove #003333;font-size:12p;padding:3px 2px 4px 1px;cursor:pointer; }

.botom { position:fixed;bottom:0px;width:476px;background-color:#000000;}
.imgstrcnt { font-size:9px;color:#191970;margin:3px 0 -5px 0;z-index:100; }
.imgstr{ font-size:9px;color:#2c2c2c;margin:16px 0 -5px 0;z-index:100; }
.table { position:fixed;width:476px;height:calc(100vh - {$G_RENDERHEIGHT_DEF}px); }
.tabldt{ position:fixed;width:476px;height:calc(100vh); }
.sticky thead { position: sticky; position: -webkit-sticky; top: 0;background-color:#000000;z-index:2; }
.thfix { text-align:left;font-weight:normal; }
.line  { height:22px;color:#ffffff;font-size:14px;border:0px solid #333333;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:break-all;cursor:pointer; }
.Parenline { text-align:left;color:#66cdaa;font-weight:normal;line-height:25px;cursor:pointer; }
.playline { width:40px;font-size:10px;height:22px;color:#ffffff;font-size:14px;border:0px solid #333333;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:break-all; }
.uline { height:16px;font-size:9px;border:0px solid #333333;color:#ffffff;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:break-all; }
.button{ position:fixed;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;cursor:pointer; }
.status{ position:fixed;bottom:2px;width:482px;height:18px;color:#BDBDBD;background-color:#2E2E2E;margin-top:4px;border:1.4px solid #000000;overflow:hidden;line-height:110%;resize:none; }
.close { border-style:none;width:50px;height:20px;padding:2px 0 2px;font-size:11px;cursor:pointer;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius: 4px; }

.upnUPart1 { width:70px;overflow:hidden;text-overflow:ellipsis;word-break:break-all;line-height:90%;display:inline-block; }

.box {border: 1px solid #888888;position:fixed;top:200px;left:170px;
      width:300px;height:238px;font-size:10p;z-index:120;
      background-color:#2f2f2f;
      display: none;
      }
.boxtitle { position:absolute;top:0px;left:0px;width:280px;height:18px;font-size:14px;background-color:#0000FF;color:#CEECF5;line-height:90%;text-align:left;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;padding:8px 10 2px;}
.boxline  { background-color:#000000;color:#ffffff;font-size:14px;font-weight:bold;text-decoration:none;white-space:nowrap;display:block;text-align:left;vertical-align:middle;cursor:pointer;border: 1px solid #acacac;width:300px;height:25;padding:3px 10 2px;}
.boxtext  { position:absolute;background-color:#2f2f2;color:#ffffff;font-size:12x;font-weight:bold;text-decoration:none;display:block;text-align:left;vertical-align:middle;border: 1px solid #ccc;width:278px;height:19;padding:3px 10 2px;}
.boxcls   { position:absolute;bottom:10px;right:10px;-moz-border-radius:5px;-webkit-border-radius:5px;border-radius:5px;cursor:pointer; }
.boxinfo  { position:absolute;left:5px;height:12;width:300px;border:0px;word-wrap:break-word;overflow:hidden;font-size:9px;-webkit-transform:scale(0.95);}

.msgbox  { position:fixed;top:200px;background-color:#f5f5f5; }
.msgcont { position:absolute;top:60px;left:20px;overflow:hidden;word-wrap:break-word;white-space:pre-wrap;text-overflow:ellipsis;text-align:center;font-size:15px;line-height:20px;color:#000000; }

/* Folder Icon */
.folder-container { width:40px;height:40px;display:flex;align-items:center;justify-content:center;background-color:#000000;border: 1px solid #696969;box-sizing: border-box; }
.folder { font-size:24px;position: absolute;width: 0.8em;height:0.4em;background-color:#39a9d6;border-radius: 0.1em 0.1em 0 0;margin: -28px 0 0px -19px; }
.folder::before { content:"";position: absolute;top: 0.3em;left:0;width:1.6em;height:1.3em;background-color:#39a9d6;border-radius: 0 0.1em 0.1em 0.1em; } /* 本体 */
/* Folder:Parent Icon */
.Parent-container { width:40px;height:40px;display:flex;align-items:center;justify-content:center;background-color:#000000;border: 1px solid #696969;box-sizing: border-box; }
.Parent { font-size:24px;position: absolute;width: 0.8em;height:0.4em;background-color:#33cc99;border-radius: 0.1em 0.1em 0 0;margin: -28px 0 0px -19px; }
.Parent::before { content:"";position: absolute;top: 0.3em;left:0;width:1.6em;height:1.3em;background-color:#33cc99;border-radius: 0 0.1em 0.1em 0.1em; } /* 本体 */

.Parentpl-container { display: flex;align-items: center;justify-content: center;width: 39px;height: 25px;background-color: #001e43;border-radius: 10%;cursor: pointer;transition: background-color 0.3s ease;margin:0px 0px 0px 0px;position:relative; }
.Parentpl-container:hover { background-color:#165e83; }
.Parentpl-container::before { content:'';position:absolute;width:0;height:0;border-left:14px solid #3cb371;border-top: 6px solid transparent;border-bottom: 6px solid transparent; }
/* Renderer Icon */
.renderer-overlay { position: absolute;top:-12px;left:50%;transform: translateX(-50%);font-size: 9px;line-height:9px;color:#fffff0;text-align:center;margin: 16px 0 0px 0px; }
.renderer-speaker { width:40px;height:40px;position:relative;background-color:#696969;border-radius:6px;margin:-2px 0 0px 0px; }
.speaker { position: absolute;background-color:#333333;border: 1px solid #cccccc;border-radius: 50%; } /*  スピーカー部分の中心を黒に設定 スピーカー部分の白枠を追加 */
.speaker.large { width:24px;height:24px;top:30%;left:16%; }
.speaker.small { width:8px;height:8px;top:5%;left:7%; }
.speaker.large::after,
.speaker.small::after { content:"";width:40%;height:40%;background-color:#cccccc;border-radius:50%;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%); } /* 白丸の直径をスピーカーのサイズに対する割合で指定,白丸の色を白に設定 */
/* MediaServer */
.hdd { width: 40px;height:40px;display:flex;align-items:center;justify-content:center;background-color:#39a9d6;border-radius:2px;border-radius:6px;position:relative; }
.hdd::before { content: "";width:32px;height:32px;background-color:#39d3d6;border:0.6px solid #000000;border-radius:50%;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%); }
.hdd::after  { content: "";width: 4px;height:4px;background-color:#333333;border-radius: 50%;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%); }
.triangle { width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:16px solid #333333;position:absolute;top:74%;left:14%;transform:translateY(-50%) rotate(45deg) }
/* Play Icon */
.play-container { display: flex;align-items: center;justify-content: center;width: 41px;height: 25px;background-color: #001e43;border-radius: 10%;cursor: pointer;transition: background-color 0.3s ease;margin:-2px 0px 0px -2px;position:relative; }
.play-container:hover { background-color:#165e83; }
.play-container::before { content:'';position:absolute;width:0;height:0;border-left:14px solid #add8e6;border-top: 6px solid transparent;border-bottom: 6px solid transparent; }
.media-icon { display:inline-block;width:36px;height:37px;background-color:#1e90ff;border-radius:6px;position:relative;margin:20px;text-align:center;color:white;font-family:Arial,sans-serif;font-size:10px;line-height:40px;margin:-2px 0px 0px 1px; }
.media-icon::before { content:'';position:absolute;top:50%;left:50%;transform: translate(-50%,-50%);width:25px;height:25px;background-color:#191970;border-radius:50%;box-shadow:0 0 0 2px #b0e0e6, 0 0 0 4px #191970; }
.media-icon::after  { content:'';position:absolute;top: 50%;left: 50%;transform:translate(-50%,-50%);width:8px;height:8px;background-color:#b0e0e6;border-radius:50%; }
.music-icon { position:relative;top:-19px;left:-18px;display:inline-block;width:36px;height:37px;background-color:#228b22;border-radius:6px;position:relative;margin:20px;text-align:center;color:white;font-family:Arial,sans-serif;font-size:10px;line-height:40px; }
.music-icon::before { content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:25px;height:25px;background-color:#2f4f4f;border-radius:50%;box-shadow: 0 0 0 2px #b0e0e6, 0 0 0 4px #191970; }
.music-icon::after { content:'';position: absolute;top:50%;left: 50%;transform: translate(-50%,-50%);width:0;height:0;border-style:solid;border-width: 5px 0 5px 10px;border-color:transparent transparent transparent #fff; }
/* NowPlay No Image */
.mediaplay { width: 125px;height: 125px;border: 6px solid #404040;border-radius: 10px;position: relative;display: flex;justify-content: center;align-items: center;opacity: 0.7; }
.mediaplay::before { content: '';width: 0;height: 0;border-top: 30px solid transparent;border-bottom: 30px solid transparent;border-left: 50px solid #404040;position: absolute;left: 35%;opacity: 0.7; }
.playboxdiv { padding: 6px 0 0 0; }  /* Playbox mediaplay使用時のdiv 補正 */
/* NowPlay Header Text */
.nowplayhd  { width:322px;height:31px;font-size:12px;text-align:left;padding:4px 4px 0;line-height:13px;word-break: break-all;white-space: normal;overflow: hidden;text-overflow: ellipsis;border:1px solid #252525; }
.nowplayhds { width:322px;height:30px;font-size:11px;text-align:left;padding:6px 4px 0;line-height:14px;word-break: break-all;white-space: normal;overflow: hidden;text-overflow: ellipsis; }
/* NowPlay Play Panel */
/* Reverse Skip */
.RSkip { width:14px;height:14px;background-color:transparent;position:relative; }
.RSkip::before { content:'';position:absolute;top:3px;left:10px;width:2px;height:13px;background-color:black; }	/* RSkip縦棒 */
.RSkip::after  { content:'';position:absolute;top:3px;left:12px;width:0;height:0;border-style:solid;border-width:7px 10px 7px 0;border-color:transparent black transparent transparent; }	/* 三角形 */
/* Reverse Fast */
.RF { width:14px;height:12px;background-color:transparent;position:relative; }
.RF::before { content:'';position:absolute;width:0;height:0;border-style:solid;border-width:7px 7px 7px 0;border-color:transparent black transparent transparent;top: 3px;left:8px; }	/* 左の三角形 */
.RF::after  { content:'';position:absolute;width:0;height:0;border-style:solid;border-width:7px 7px 7px 0;border-color:transparent black transparent transparent;top: 3px;left:15px; }	/* 右の三角形 */
/* Pause */
.Pause { width:24px;height:24px;background-color:transparent;position:relative; }
.Pause::before { content:'';position:absolute;top:2px;left:9px;width:3px;height:14px;background-color:black; }	/* 左の縦棒 */
.Pause::after  { content:'';position:absolute;top:2px;left:16px;width:3px;height:14px;background-color:black; }	/* 右の縦棒 */
/* Stop */
.Stop { width:24px;height:24px;background-color:transparent;position:relative; }
.Stop::before { content:'';position:absolute;top:4px;left:9px;width:12px;height:12px;background-color:black; }
/* Play */
.Play { width:40px;height:40px;background-color:transparent;position:relative; }
.Play::before { content:'';position:absolute;top:3px;left:9px;width:0;height:0;border-style:solid;border-width:7px 0 7px 14px;border-color:transparent transparent transparent black; }	/* 三角形 */
/* Forward Fast */
.FF { width:14px;height:12px;background-color:transparent;position:relative; }
.FF::before { content:'';position:absolute;width:0;height:0;border-style:solid;border-width:7px 0 7px 7px;border-color:transparent transparent transparent black;top:3px;left:10px; }	/* 左の右向き三角形 */
.FF::after  { content:'';position:absolute;width:0;height:0;border-style:solid;border-width:7px 0 7px 7px;border-color:transparent transparent transparent black;top:3px;left:17px; }	/* 右の右向き三角形 */
/* Forward Skip */
.FSkip { width: 14px;height: 14px;background-color: transparent;position: relative; }
.FSkip::before { content:'';position:absolute;top:3px;left:19px;width:2px;height:13px;background-color:black; }	/* 縦棒 */
.FSkip::after  { content:'';position:absolute;top:3px;left:9px;width:0;height:0;border-style:solid;border-width:7px 0 7px 10px;border-color:transparent transparent transparent black; } 		/* 三角形 */
/* Button */
.PlayBox { position:fixed;top:30px;height:206px;background-color:#000000;width:481px;z-index:1;border:1px solid #2c2c2c; }
.PlayPanel { position:fixed;top:212px;width:30px;height:20px;background-color:#a9a9a9;border:1px solid #646464;border-radius:2px;cursor:pointer; }
.PlayPanel:active{ background:#696969; }
.PlayPantp { top:35px;left:384px;width:78px;background-color:#33cc99; }
.PlayCmd { position:fixed;top:212px;width:38px;height:16px;padding:2px 0 2px;font-size:11px;border:1px solid #191970;border-radius:3px;background-color:#2d2dff;color:#CED8F6;text-align:center;cursor:pointer; }
.PlayCmd:active{ background:#00008b; }
.PlayCls { position:fixed;top:212px;width:38px;height:16px;padding:2px 0 2px;font-size:11px;background-color:#a9a9a9;-webkit-border-radius:4px;border-radius:4px;text-align:center;cursor:pointer; }
.PlayTx { position:fixed;top:212px;width:80px;height:20px;font-size:9px;color:#dddddd;background-color:#2b2b2b;padding:1px 4px 0px;line-height:9px;border:1px solid #646464;border-radius:4px; }
.playfot { width:464px;height:18px;background-color:#252525;color:#b0b0b0;font-size:9px; border-radius: 0 0 4px 4px;padding:3px 0 0 10px; }
.playline { height:22px;color:#ffffff;font-size:14px;;text-align: center;border:0px solid #333333;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;word-break:break-all; }
.Playhd { position:fixed;top:60px;height:20px;font-size:9px;visibility:hidden; }

.PlayMini { top:36px;left:468px;width:20px;height:18px;white-space:normal;background-color:#008000;color:#CED8F6;border-style:none;font-size:9px;cursor:pointer;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;text-align:center;padding:2px 0px 0px 1px; }

/* #idlodingmsg   : 起動待ちディスプレイ @keyframes spin: 起動待ちディスプレイアクション */
#idlodingmsg { position:fixed;top:210px;left:210px;z-index:4;width:50px;height:50px;border-radius:50%;border:8px solid #ffffff;border-right-color:transparent;animation:spin 1s linear infinite;display:none; }
@keyframes spin { 0% { transform: rotate(0deg);   opacity: 0.2; } 50%  { transform:rotate(180deg);opacity:1.0; } 100% { transform:rotate(360deg);opacity:0.2; } }

</style>
EOM;
	DispUPnPHeader();						// Display : Header

	echo <<<EOM
<div id="idlodingmsg"></div>
EOM;

	$mesgon=0;
	if($G_MAKESSDP=="on") {
		$file_exists=getUPnPDevicesConf();		// SSDP Request Result File Cheack
		if ($file_exists===false) {
			 $mesgon=2;
			 DispUPnP_initmsgOn($mesgon,"Create SSDP Request Result file","#eee8aa");
			 }
		else {
			 DispUPnP_initmsgOn($mesgon,"Get UPnP Server List....","#ffffff");
			 }
		}
	else{
		DispUPnP_initmsgOn($mesgon,"SSDPCast Request","#eee8aa");
		}
	@ob_flush();		// HeaderOut
	@flush();

	$result_sock=getUPnPDevices($Content,$Renderer);

	DispUPnPDetaile($G_RENDERHEIGHT,$Content,$Renderer);
	DispUPnPStatus();

	DispMsgBox();							// MessageBox(Form)をDisplay:noneで作成しておく。
	if($result_sock===false) {
	echo <<<EOM
<script>
	RequestMsgBox('UPnP:WebUI RendererRequest','{$SOCK_ERRORMSG}');
</script>
EOM;

		}
echo <<<EOM
</body
</html>
EOM;

	DispUPnP_initmsgOff($mesgon);
	}

/*
	DispUPnP_initmsgOn($mesgon,$msg,$color) : SSDP Request Result file Message
	$mesgon : i  : MesageOn/Off 0:off On:表氏秒数Sec
	$msg    : i  : Message (Off 0のときは無効
	$color  : i  : Message Color
*/
function DispUPnP_initmsgOn($mesgon,$msg,$color) {
if ($mesgon > 0) {
	echo <<<EOM
<div id=idCreat class="box" style="left:100px;display:none">
<div class="boxtitle">UPnP initialize Message</div>
<div class="boxtext" style="top:100px;color:{$color};text-align:center;border:none">{$msg}</div>
</div>

<script>
document.getElementById('idCreat').style.display='block';;
</script>
EOM;
	}

	echo <<<EOM
<script>
document.getElementById('idlodingmsg').style.display='block';;
</script>
EOM;
	}

/*
	DispUPnP_initmsgOff($mesgon) : SSDP Request Result file Message off
	$mesgon : i  : Message表示秒数
*/
function DispUPnP_initmsgOff($mesgon) {
	echo <<<EOM
<script>
document.getElementById('idlodingmsg').style.display='none';
</script>
EOM;

	if ($mesgon > 0) {
	$mesgon=1000*$mesgon;
	echo <<<EOM
<script>
setTimeout(() => { document.getElementById('idCreat').style.display='none'; },{$mesgon});
</script>
EOM;
	}

	}

/* ----------------------------------------------------------------------------
	DispUPnPDetaile($G_RENDERHEIGHT,$Content,$Renderer) : Display UPnP Detaile
	$G_RENDERHEIGHT: i  : Render部Height
	$Content      : i  : Array Content
	$Renderer     : i  : Array Renderer
----------------------------------------------------------------------------- */
function DispUPnPDetaile($renderheigth,$Content,$Renderer) {
// Display : Content MediaServer
	print '<div id=idDetaile style="top:55px;width:482px;">';
	print '<div id=idUPnPtable class="scrbar table" style="top:55px;width:482px;">';
	print '<table border="3" cellpadding="0" bgcolor="#000000" frame=void style="table-layout:fixed;">';
	$imgwidth=40;
	foreach ((array)$Content["location"] as $key =>$location) {
		$postjs='EventUPnPDisp(\''.$Content["friendlyName"][$key].'\',\''.$Content["modelName"][$key].'\');EventUPnPDevices(\''.$location.'\');';
		print '<tr>';
		print '<td rowspan="2">';
		print '<div onclick="'.$postjs.'" style="font-size:11px;width:'.$imgwidth.'px;cursor:pointer;">';
		$str= "";
		if (isset($Content["icons"][$key])) {
			 $icon=$Content["icons"][$key];
			 if($icon['width'] !==$imgwidth) {
				$rate=$icon['width']/$imgwidth;
				$height=$icon['height']/$rate;
				}
			if($socket = @fopen($icon['url'], 'r')) { fclose($socket);
				 print '<img src=\''.$icon['url'].'\' width=\''.$imgwidth.'\' height=\''.$height.'\'><br>';
				 }
			else {
				 $str= "DLNA";					// URLPathにiconがない
				 }
			 }
		else {
			 $str= "DLNA";						// iconがない
			 }
		if($str !=="") {
			 print '<div align=center class="hdd" style="width:'.$imgwidth.'px;height='.$imgwidth.'px;"><div class="triangle"></div>';
			 print '<p class=imgstrcnt style="width:'.$imgwidth.'px;height:'.$imgwidth.'px;">'.$str.'</p>';
			 print '</div>';
			 }
		print '</div>';
		print '</td>';
// Upper Line
		print '<td colspan="2">';
		print '<div onclick="'.$postjs.'" class=line style="width:430px;cursor:pointer;">'.$Content["friendlyName"][$key].'</div>';
		print '</td>';
		print '</tr>';
// Under Line
		print '<tr>';
		print '<td>';
		print '<div class=uline style="width:150px;">'.$Content["baseUrl"][$key].'</div>';
		print '</td>';
		print '<td>';
		print '<div class=uline style="width:280px;">'.$Content["modelName"][$key].'</div>';
		print '</td>';
		print '</tr>';
		}
	print '</table>';
	print '</div>';
// Display : Renderer 
	print '<div id=idUPnPRender class=botom style="height:'.$renderheigth.'px;font-size:11px;z-index:400;">';
	print '<div class=headrendr>Renderer</div>';
	print '<div class=scrbar style="width:482px;height:'.($renderheigth-50).'px;margin: 2px 0 0 0;">';
	print '<table id="idRenderTB" border="3" cellpadding="0" bgcolor="#000000" frame=void style="table-layout:fixed;">';
	$imgwidth=40;
	foreach ((array)$Renderer["location"] as $key =>$location) {
		print '<tr>';
		print '<td rowspan="2">';
		$eventjs='EventRenderer(\''.$key.'\');';
		print '<div  onclick="'.$eventjs.'" style="font-size:11px;width:'.$imgwidth.'px;cursor:pointer;">';
		$str= "";$style="";
		if (isset($Renderer["icons"][$key])) {
			 $icon=$Renderer["icons"][$key];
			 if($icon['width'] !==$imgwidth) {
				$rate=$icon['width']/$imgwidth;
				$height=$icon['height']/$rate;
				}
			if($socket = @fopen($icon['url'], 'r')) { fclose($socket);
				 print '<img src=\''.$icon['url'].'\' width=\''.$imgwidth.'\' height=\'".$height."\'><br>';
				 }
			else {
				 $str= "Not Image";					// URLPathにiconがない
				 if ((isset($Renderer["modelName"][$key]))&&($Renderer["modelName"][$key] !=="")) {
					 $tmp=str_split($Renderer["modelName"][$key],5);
					 $str=$tmp[0];$pos='20';if(isset($tmp[1])) { $str=$str." ".$tmp[1];$pos='16'; }
					 $style=' style="font-size:7px;transform: scale(0.8);margin:'.$pos.'px 0px 0px -18px;"';
					 }
				 }
			 }
		else {
			 $str= "No Image";						// iconがない
			 if ((isset($Renderer["modelName"][$key]))&&($Renderer["modelName"][$key] !=="")) {
				 $tmp=str_split($Renderer["modelName"][$key],5);
				 $str=$tmp[0];$pos='20';if(isset($tmp[1])) { $str=$str." ".$tmp[1];$pos='16'; }
				 $style=' style="font-size:7px;transform: scale(0.8);margin:'.$pos.'px 0px 0px -18px;"';
				 }
			 }
		if($str !=="") {
			 print '<div align=center class="renderer-speaker" style="width:'.$imgwidth.'px;height='.$imgwidth.'px;"><div class="speaker large"></div><div class="speaker small"></div>';
			 print '<div class="renderer-overlay"'.$style.'>'.$str.'</div>';
			 print '</div>';
			 }
		print '</div>';
		print '</td>';
// Upper Line
		print '<td colspan="2">';
		print '<div onclick="'.$eventjs.'" style="font-size:14px;border:0px solid #333333;width:430px;height=26px;color:#ffffff;cursor:pointer;">'.$Renderer["friendlyName"][$key].'</div>';
		print '</td>';
		print '</tr>';
// Under Line
		print '<tr>';
		print '<td>';
		print '<div class=uline style="width:150px;">'.$Renderer["baseUrl"][$key].'</div>';
		print '</td>';
		print '<td>';
		print '<div id="modelName_'.$key.'"class=uline style="width:280px;">'.$Renderer["modelName"][$key].'</div>';
		print '<div id="Renderer_'.$key.'" class=uline style="width:0;height:0;">'.$Renderer["avTransportControlUrl"][$key].'</div>';
		print '<div id="Manager_'.$key.'"  class=uline style="width:0;height:0;">'.$Renderer["connectionManagerUrl"][$key].'</div>';
		print '</td>';
		print '</tr>';
		}
	print '</table>';
	print '</div>';
	print '</div>';
	print '</div>';
	}

/* ----------------------------------------------------------------------------
	DispUPnPHeader()
----------------------------------------------------------------------------- */
function DispUPnPHeader() {
	global $MY_VER_UPNP,$G_CASTCONF,$G_SYSVMSG;global $G_EXTBTN,$G_MAKESSDP;	// 拡張FunctionButtonName:2文字

	$libxml="libxml (".LIBXML_DOTTED_VERSION.")&nbsp:used:SimpleXML";
	$phpver="PHP Ver: ".phpversion();
	$mem= sprintf('%.2f',((memory_get_usage()) / 100000));
	$mem="PHP Used Memory : ".$mem." MB";
	$server=$_SERVER["SERVER_ADDR"];
	$remote=$_SERVER["REMOTE_ADDR"];
	$sysvmsg = $G_SYSVMSG;
	if($sysvmsg !=="") {
		 $sysvmsg="System V [".$sysvmsg." : Installed]";
		 $cfinf='';$css_server="";
		 }
	else {
		 $sysvmsg="System V [ sysvmsg : Not Install ]";
		 $cfinf='<div class="boxtext boxinfo" style="bottom:14px;color:#ffd700;">*ServerPlay, Stop Process Restriction</div>';$css_server="color:#c0c0c0;";
		 }
	$castfile="SSDP:".basename($G_CASTCONF);
	if (file_exists($G_CASTCONF)) {
		 $castfile=$castfile." ".date("Y-m-d H:i:s",filemtime($G_CASTCONF));
		 }
	else {
		 $castfile=$castfile." (not Make)";
	 	 }
	$func_close="";$head_defleft=0;$headfunc=468;$headplay=384;$headrender=240;
	if((isset($_GET["frame"]))&&($_GET["frame"]=="on")) {
		$head_defleft=20;
		$headfunc=$headfunc-($head_defleft+3);
		$headplay=$headplay-$head_defleft;
		$headrender=$headrender-($head_defleft+2);
		$func_close = <<<EOM
	<button onclick="EventSelfClose();" class="headCls">X</button>
<script>
if (window.parent && typeof window.parent.mpdWatchSet === "function") {		// WatchStatus()のSet/Reset Frameなどでreloadさせない
	window.parent.mpdWatchSet(0);
	}
</script>
EOM;
		}
	if($G_MAKESSDP =="on") {												// on:SSDPCastのfile[config.txt]を使用する
		$funccast='<button type=submit onClick="EventUPnPGet(1);document.getElementById(\'idhfunc\').style.display=\'none\';" class="boxline" style="position:absolute;left:0px;top:32px;">Make UPnP</button>';
		}
	else{
		$funccast='<button type=submit class="boxline" style="position:absolute;left:0px;top:32px;background-color:#282828;color:#d5d5d5;border: 1px solid #555555;">Do not use UPnP:SSDP File</button>';
		}
	global $G_MPDCLI,$G_MPDCLICONF,$G_MPDSET,$G_MPDCONF,$G_SUBWINTOP;	// 外部モジュール
	$func1="";$subcls="";
	if ((file_exists($G_MPDCLI))&&(file_exists($G_MPDCLICONF))) {
		$func1='<button type=submit onClick="subwin=window.open(\''.$G_MPDCLI.'\',\'mpdupnp-subwin\',\'left=\'+(window.screenX)+\',top=\'+(window.screenY+'.$G_SUBWINTOP.')+\',replace=yes,width=486,height=500;\');" class="boxline" style="position:absolute;left:0px;top:72px;">upmpdcli-setting (This Server)</button>';
		$subcls="  if (subwin && !subwin.closed) { subwin.close(); }";
		}
	$func2="";
	if ((file_exists($G_MPDSET))&&(file_exists($G_MPDCONF))) {
		$func2='<button type=submit onClick="subwin=window.open(\''.$G_MPDSET.'\',\'mpdupnp-subwin\',\'left=\'+(window.screenX)+\',top=\'+(window.screenY+'.$G_SUBWINTOP.')+\',replace=yes,width=486,height=500;\');" class="boxline" style="position:absolute;left:0px;top:102px;">ALSA:mpd.conf-setting (This Server)</button>';
		if($subcls=="") {
			$subcls="  if (subwin && !subwin.closed) { subwin.close(); }";
			}
		}
	echo <<<EOM
<!-- Header Common -->
	<div class=head>
	<div class=headline>
	<button id=idGetUPnP onclick="EventUPnPGet(2);" class=button style="top:5px;left:12px;width:78px;font-size:12px;">Get UPnP</button>
	<div class=headTi style="left:88px;width:40px;">DMR</div>
	<div id=idSelRender class=headtx style="left:128px;width:{$headrender}px;"></div>
	<button id=idNowPlay onclick="EventStateRequest();" class=button style="position:fixed;top:5px;left:{$headplay}px;width:78px;height:22px;font-size:12px;">Play</button>
	<div id=idSelRow class=headhd style="left:370px;width:20px;"></div>
	<div id=idSelcontUrl class=headhd style="top:12px;left:370px;width:100px;font-size:9px;"></div>
	<div id=idManagerUrl class=headhd style="top:12px;left:370px;width:100px;font-size:9px;"></div>
	<div id=idSelmodelName class=headhd style="top:24px;left:370px;width:100px;font-size:9px;"></div>
	<div id=idSelImage class=headhd style="top:24px;left:370px;width:100px;font-size:9px;"></div>
	<button class=headfun onclick="document.getElementById('idhfunc').style.display='block';" style="left:{$headfunc}px;">...</button>
	{$func_close}
	</div>
	<div id=idServName class=headsub style="position:fixed;top:33px;left:8px;">MediaServer</div>
	<div id=idSelUPnPURL class=headhd style="top:36px;left:340px;width:100px;"></div>
	<div id=idSelCName class=headhd style="top:36px;left:380px;width:50px;"></div>
	<div id=idSelPid class=headhd style="top:36px;left:440px;width:50px;"></div>
	<div id=idServereptitle class=headsbt style="position:fixed;top:37px;left:304px;{$css_server}"></div>
	<input id=idServer type="checkbox" onclick="" style="position:fixed;top:36px;left:360px;width:10px;display:none;"><!-- Server Play Mode -->
	<button id=idParent onclick="" class="button PlayPantp" style="display:none;">ParentTop</button>
  <button id=idMini onclick="ArgOptMiniDLNA()" class="button PlayMini" style="display:none;">{$G_EXTBTN}</button><!-- 拡張Function -->
	</div>
<!-- Now Music -->
	<div id=idPlayBox class=PlayBox style="display:none;z-index:401">
	<div class=headsub>Playing to Renderer</div>
	<div id=idPlaydtl style="position:absolute;top:30px;height:500px;color:#ffffff;width:480px;"></div>
	<div onclick="RequestPBoxPlay('RSkip');" class="PlayPanel RSkip" style="left:  6px;"></div>
	<div onmousedown="EventPBFFTimer('REW');" onmouseup="EventPBFFReset();" class="PlayPanel RF"    style="left: 40px;"></div>
	<div id=idCmdPause onclick="RequestPBoxPlay('Pause');" class="PlayPanel Pause" style="left: 74px;"></div>
	<div id=idCmdStop  onclick="RequestPBoxPlay('Stop');"  class="PlayPanel Stop"  style="left:108px;"></div>
	<div id=idCmdPlay  onclick="RequestPBoxPlay('Play');"  class="PlayPanel Play"  style="left:142px;"></div>
	<div onmousedown="EventPBFFTimer('FFW');" onmouseup="EventPBFFReset();" class="PlayPanel FF"    style="left:176px;"></div>
	<div onclick="RequestPBoxPlay('FSkip');" class="PlayPanel FSkip" style="left:210px;"></div>
	<div class="PlayTx" style="left:245px;">May be valid <BR>&nbsp;for Each Model</div>
	<div onclick="RequestPBoxPlay('Clear');" class="PlayCmd" style="left:336px;">Clear</div>
	<div onclick="RequestPBoxPlay('ErrorClear');" class="PlayCmd" style="left:380px;">EClear</div>
	<div onclick="_EventPBoxClose();" class="PlayCls" style="left:438px;">Close</div>
	<div id=idPBUrl class=Playhd  style="left:480px;width:0px;"></div>
	<div id=idPlaylist class="scrbar tabldt" style="top:236px;width:482px"></div>
	</div>
<!-- Function Box -->
	<div id=idhfunc class=box style="position:fixed;top:30px;left:186px;height:290px;z-index:500;">
	<div align="left" class=boxtitle>Function UPnP</div>
	{$funccast}
	{$func1}
	{$func2}
	<div class="boxtext boxinfo" style="top:136px;left:0px;">Information</div>
	<div class="boxtext boxinfo" style="top:150px;">Host:{$server} / Remote:{$remote}</div>
	<div class="boxtext boxinfo" style="top:164px;">{$phpver}</div>
	<div class="boxtext boxinfo" style="top:178px;">{$mem}</div>;
	<div class="boxtext boxinfo" style="top:192px;color:#98fb98;">{$MY_VER_UPNP}</div>
	<div class="boxtext boxinfo" style="top:210px;">{$libxml}</div>
	<div class="boxtext boxinfo" style="top:224px;">{$sysvmsg}</div>
	<div class="boxtext boxinfo" style="top:238px;">{$castfile}</div>
	{$cfinf}
	<input type="button" name="Submit" value="Cancell" onclick="document.getElementById('idhfunc').style.display='none';ArgSubWinClose();" class="boxcls" />	
</div>
<script>
/* ----------------------------------------------------------------------------
	Function Close SubWindow
----------------------------------------------------------------------------- */
// EventListener:終了時(閉じるとき)
window.addEventListener('beforeunload', function () {
    ArgSubWinClose();
	});

/*
	ArgSubWinClose() : Close SubWindow
*/
function ArgSubWinClose() {
	{$subcls}
	}

/* ----------------------------------------------------------------------------
	拡張Function Global JSP : G_MINIURL:Linkage URL <<拡張Function>>
----------------------------------------------------------------------------- */
G_MINIURL='';		//G_MINIURL='./mpdminidlnadb.php';
var G_MINIWIN;
window.addEventListener("beforeunload", () => {
    if (G_MINIWIN && !G_MINIWIN.closed) { G_MINIWIN.close(); }
	});

/*
	ArgOptMiniDLNASet(url) : 拡張FunctionSet(JSP): Postで設定
	url     : i   : 拡張URL
	title   : i   : 拡張Button
*/
function ArgOptMiniDLNASet(url,title) {
	document.getElementById('idMini').style.display='block';
document.getElementById('idMini').textContent = title;
	G_MINIURL=url;
	}

/*
	ArgOptMiniDLNASet(url) : 拡張FunctioRenSet(JSP): 初期画面状態
*/
function ArgOptMiniDLNAReSet() {
	var objMini=document.getElementById('idMini');
	objMini.style.display='none';
	G_MINIURL="";
    if (G_MINIWIN && !G_MINIWIN.closed) { G_MINIWIN.close(); }
	}

/*
	ArgOptMiniDLNA() : 拡張Function:onclick
*/
function ArgOptMiniDLNA() {
	G_MINIWIN=window.open(G_MINIURL,'mpdsetting','left='+(window.screenX)+',top='+(window.screenY+110)+',replace=yes,width=476,height=500;');
	}

</script>
EOM;
	}

/* ----------------------------------------------------------------------------
	DispUPnPStatus($message="") : Status Line Display
	$message ; i  ; Status Message 
----------------------------------------------------------------------------- */
function DispUPnPStatus($message="") {
echo <<< EOM
<div class=botom style="height:20px;font-size:11px;width:482px;z-index:402;">
<textarea onclick="this.blur();" cols="30" rows="1" readonly="" id="stdout" class=status>
{$message}
</textarea>
</div>
EOM;
	}

/* ----------------------------------------------------------------------------
	DispMsgBox() : Display MessageBox (Init)
----------------------------------------------------------------------------- */
function DispMsgBox() {
	$width=360;
	print '<div id=idMsgBox class="box msgbox" style="width:'.$width.'px;height:160px;left:'.((480-$width)/2).'px;z-index:1000;display:none;">';
	print '<div id=idMsgBoxtitle class=boxtitle align=left style="width:'.($width-20).'px;cursor:pointer;"></div>';
	print '<div id=idMsgBoxstr align=center class=msgcont style="width:'.($width-40).'px;height:100px;"></div>';
	print '<input type="button" name="Submit" value="OK" onclick="document.getElementById(\'idMsgBox\').style.display=\'none\';" style="position:absolute;top:120px;left:'.(($width/2)-20).'px;font-size:12px;width:60px;height:24px;" />';
	print '</div>';
	}

/* ****************************************************************************
	Common Function
	 ArgtimeToSec()      : Time to Sec
	 ArgDateParse()      : Parse From Date to year
	 ArgArraySort()      : PHP Sort 複数KeyArray (Ascending)
	 ArgNKeyArrayMarge() : NumericKey,複数KeyArray Marge
***************************************************************************** */
/* ----------------------------------------------------------------------------
	ArgtimeToSec($time) : Time to Sec
	time      : i  : hh(h):mm:ss
	@return   : o  : Seconds
----------------------------------------------------------------------------- */
function ArgtimeToSec($time) {
	list($h, $m, $s) = explode(":", $time);
	$sec=$h * 3600 + $m * 60 + $s;
	return ($sec);
	}

/* ----------------------------------------------------------------------------
	ArgDateParse($date) : DateParse year
	$date     : i  : {y--y}-mm-dd , {y--y}/mm/dd
	@return   : o  : year
----------------------------------------------------------------------------- */
function ArgDateParse($date) {
	$result="";
	preg_match('/^(\d+)[\-\/]/', $date, $m);
	if (!empty($m[1])) {
		$result=$m[1];
		}
	return($result);
	}

/* ----------------------------------------------------------------------------
	ArgArraySort($ary,$sortkey) : 複数KeyArray Sort Ascending
	$ary     : Sort対象Array
	$sortkey : Sort対象Key
	@return  : Sort後Array
----------------------------------------------------------------------------- */
function ArgArraySort($ary,$sortkey) {
	$arykey=array_keys($ary);			// 複数Keyを配列化
	if (!in_array($sortkey,$arykey)) {	// $sortkeyが$aryにあるか?
		return($ary);
		}
	$arysort=$ary[$sortkey];			// $aryresult:Result array

	foreach ($arysort as $key=>&$val) {	// 全て大文字に変換
		$val=strtoupper($val);
		}
	asort($arysort);					// aSort , Keyは変更しない
	$aryresult=array();
	foreach ($arysort as $key=>$val) {	// $aryresult:Result array
		foreach ($arykey as $akey) {
			$aryresult[$akey][]=$ary[$akey][$key];	// 全てのKeyをSort対象Sort後に従い作成
			}
		}
	return($aryresult);
	}

/* ----------------------------------------------------------------------------
	ArgNKeyArrayMarge($dest,$sourse) : NumericKey,複数KeyArray Marge
	$dest   : i  : Base Array
	$sourse : i  : Array (Add)
	@return : o  : Marge Array
	数字をKeyの配列(以下多次元Array)をMargeする
----------------------------------------------------------------------------- */
function ArgNKeyArrayMarge($dest,$sourse) {
	$result=$dest;
	$addkey=count($result);			// 数値Keyなので最大から追加
	foreach($sourse as $key=>$ary) {
		if(!is_array($ary)) {			// Arrayでない場合はそのまま追加
			$result[$addkey]=$ary;	
			$addkey=$addkey+1;
			continue;
			}
		$arykey=array_keys($ary);			// 複数Keyを配列化
		foreach($arykey as $val) {
			$result[$addkey][$val]=$ary[$val];
			}
		$addkey=$addkey+1;
		}
	return($result);
	}

/* ****************************************************************************
	UPnP Library Used : socketLibrary / simplexml
	i/F UPnP Server Control
	 getUPnPDevices()     : Make UPnP Server Device Array (Contents/Renderer)
	 getCastUPnPDevices() : Cast SSDP UPnP Make Device List
	 getDeviceType( )     : Get UPnP Server Device Type
	 getDeviceSocket()    : SSDPRequest Send Sock (Get Content Directory ServiceURL)
	※CommonFunctionは概要除く
	※ CURL / UPnPLibは未使用
***************************************************************************** */
/* ----------------------------------------------------------------------------
	getUPnPDevices(&$Content,&$Renderer,$make) : UPnP Device List  << User I/F >>
	$Content,: io : Array ContentDirectory
	$Renderer: io : Array Renderer
	$make    : o  : 0:無条件でMulticast 1:Multicast後，結果をFile化 2:Multicast Fileがあれば使用する
	※設定ファイル作成指示を作成する。(Multicastしたくない，また余計なデバイスを表示しない)
----------------------------------------------------------------------------- */
function getUPnPDevices(&$Content,&$Renderer,$make=2) {
	global $G_MAKESSDP;

	$Content["location"]=array();$Renderer["location"]=array();
	$file_exists=getUPnPDevicesConf();

	if (($G_MAKESSDP !=="on")||($make == 0)||($make == 1)||(($make == 2)&&($file_exists===false))) {
		$alllocations = getCastUPnPDevices();			// Multicast
		if($alllocations==false) {
			return(false);
			}
		}
	if(($make == 2)&&($file_exists===true)) {			// 2:Multicast Fileがあれば読み込む
		$alllocations=getUPnPDevices_get();
		}
	$Locations=array();
	foreach ($alllocations as $locat) {					// 重複locationを除く
		if (!in_array($locat,$Locations)) {
			$Locations[]=$locat;
			}
		}
	if (($G_MAKESSDP =="on")&&(($make == 1)||($file_exists===false))) {			// 1:MulticastをFile化
		getUPnPDevices_put($Locations);
		}
	foreach ($Locations as $location) {					// Multicast UPnP DeviceをContent,Rendererに区別
		$info = getDeviceType($location);
		if (isset($info['error'])) {
			echo "Error: {$info['error']}\n";
			continue;
			}
		if (!empty($info) && $info['isContentDirectory'] === true) {
			if (!in_array($location,$Content["location"])) {
				$Content["location"][]=$location;
				$Content["baseUrl"][]=$info['baseUrl'];
				$Content["deviceType"][]=$info['deviceType'];
				$Content["friendlyName"][]=$info['friendlyName'];
				$Content["manufacturer"][]=$info['manufacturer'];
				$Content["modelName"][]=$info['modelName'];
				$Content["icons"][]=$info['icons'];
				$Content["services"][]=$info['services'];
				}
			}
		if (!empty($info) && $info['isRenderer'] === true) {
			if (!in_array($location,$Renderer["location"])) {
				$Renderer["location"][]=$location;
				$Renderer["baseUrl"][]=$info['baseUrl'];
				$Renderer["deviceType"][]=$info['deviceType'];
				$Renderer["friendlyName"][]=$info['friendlyName'];
				$Renderer["manufacturer"][]=$info['manufacturer'];
				$Renderer["modelName"][]=$info['modelName'];
				$Renderer["icons"][]=$info['icons'];
				$Renderer["services"][]=$info['services'];
				$Renderer["avTransportControlUrl"][]=$info['avTransportControlUrl'];
				$Renderer["connectionManagerUrl"][] =$info['connectionManagerUrl'];
				}
			}
		}
	$Content=ArgArraySort($Content,"friendlyName");
	$Renderer=ArgArraySort($Renderer,"friendlyName");
	return(true);
	}

/*
	getUPnPDevicesConf() : InvalidCheack ,Config File
	@return : o  : True:valid , False:Invalid
*/
function getUPnPDevicesConf() {
	global $G_CASTCONF;

	$nofilesize=20;
	if (!file_exists($G_CASTCONF)) {
		return(false);
		}
	$size=filesize($G_CASTCONF);
	if ($size < $nofilesize) {						// 指定Byte以下のファイルはなしの扱い
		return(false);
		}
	return(true);
	}

/*
	getUPnPDevices_get() : Get Results ,SSDP Request
	@return : o  : $alllocations Array : Results ,SSDP Request
*/
function getUPnPDevices_get() {
	global $G_CASTCONF;

	$fp = fopen($G_CASTCONF,"r");
	if ($fp){
	 	while (($buffer = fgets($fp)) !== false) {
			$alllocations[]=rtrim($buffer,"\n");
			}
		}
	fclose($fp);
	return($alllocations);
	}

/*
	getUPnPDevices_put($Locations) : Make Results ,SSDP Request
	$Locations: i  : $Locations Array : Results ,SSDP Request
*/
function getUPnPDevices_put($Locations) {
	global $G_CASTCONF;

	$backfile=$G_CASTCONF.".bak";
	if (file_exists($G_CASTCONF)) {				// 面倒だかSambaParaの簡易な設定に合わせRewriteを避ける
		if (file_exists($backfile)) {
			unlink($backfile);
			}
		if (!rename($G_CASTCONF,$backfile)) {
			print " UPnP:UPnPDevices_put failed to back($backfile)\n";
			}
		}
	$fp = fopen($G_CASTCONF,"w");
	foreach($Locations as $location) {
		fwrite($fp,$location."\n");
		}
	fclose($fp);
	}

/* ----------------------------------------------------------------------------
	getCastUPnPDevices($searchTarget,$timeout) : UPnP Device List:<< Multicast>>
	$searchTarget : i  : 'ssdp:all'
	$timeout      : i  : 2
	@return       : o  : location (url)
----------------------------------------------------------------------------- */
function getCastUPnPDevices($searchTarget = 'ssdp:all', $timeout = 2) {
	global $SOCK_ERRORMSG;

	$multicastAddress = '239.255.255.250';
	$multicastPort = 1900;
	$ssdpRequest ="M-SEARCH * HTTP/1.1\r\n" .
				  "HOST: $multicastAddress:$multicastPort\r\n" .
				  "MAN: \"ssdp:discover\"\r\n" .
				  "MX: 1\r\n" .
				  "ST: $searchTarget\r\n\r\n";
	ini_set('display_errors', 1);
	error_reporting(E_ALL); // エラー報告レベル設定
	$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
	if ($socket === false) {
		$SOCK_ERRORMSG=  "mpdupnp : Socket create failed:[".socket_strerror(socket_last_error())."]<BR>Check php.ini extension=sockets";
		return(false);
		}
	socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);		// SSDPリクエストを送信
	socket_bind($socket, '0.0.0.0', 0);
    clearSocketBuffer($socket);										// SocketBuffer clear

	$success=socket_sendto($socket, $ssdpRequest, strlen($ssdpRequest), 0, $multicastAddress, $multicastPort);
	if (!$success) {
		$SOCK_ERRORMSG=  "mpdupnp : socket_sendto : SSSDP Request,false";
		return(false);
		}
	$startTime = time();
	$responses = array();
	while ((time() - $startTime) < $timeout) {
		$read = array($socket);
		$write = $except = null;
		$tv = array('sec' => $timeout, 'usec' => 0);
		if (socket_select($read, $write, $except, $tv['sec'], $tv['usec'])) {
			$buffer = '';
			$from = '';
			$port = 0;
			socket_recvfrom($socket, $buffer, 1024, 0, $from, $port);	//SSDPレスポンスの受信(socketから受信)
			if (preg_match('/LOCATION:\s*(.*)/i', $buffer, $matches)) {
				$responses[] = trim($matches[1]);
				}
			}
		}
	socket_close($socket);
	ini_set('display_errors', 0);
	return($responses);
	}

/*
	clearSocketBuffer($socket) : SocketBuffer clear
	$socket : i  : 
*/
function clearSocketBuffer($socket) {
    socket_set_nonblock($socket);   // NonBlock Modeに設定
    $buffer = '';
    $from = '';
    $port = 0;
    while (@socket_recvfrom($socket, $buffer, 1024, MSG_DONTWAIT, $from, $port) > 0) {		 // Socketに残っているデータを読み取ってclear
	    }
    socket_set_block($socket);		// Block Modeに戻す
	}

/* ----------------------------------------------------------------------------
	getDeviceType($url) : UPnP Device Type
	$url      : i  : location (url)
	@return   : o  : Array[deviceType]         : $xml->device->deviceType;
	                      [isRenderer]         : valid:isRenderer
	                      [isContentDirectory] : valid:isContentDirectory
	                      [services]           : array[$service]serviceType;
----------------------------------------------------------------------------- */
function getDeviceType($url) {
//	$xml = @simplexml_load_file($url);				// Socketを使用せずにTest時使用
	$xml = getDeviceSocket($url,$errstr);
	if($xml==false){								// SocketError & XML変換Error
		return(false);
		}
	if (!$xml) return array('error' => 'Unable to load XML');
	$deviceType = (string)$xml->device->deviceType;
    $baseUrl = parse_url($url);						 // ベース URL を取得 (ホスト名 + ポート番号)
	$str="";
	if(isset($baseUrl['port'])) {
		$str=":".$baseUrl['port'];
		}
    $baseUrl = $baseUrl['scheme'] . '://' . $baseUrl['host'] . $str;

	$friendlyName = (string)$xml->device->friendlyName;
	$manufacturer = (string)$xml->device->manufacturer;
	$modelName = (string)$xml->device->modelName;

	$services = array();
	$avTransportControlUrl = null;
	$connectionManagerControlUrl = null;
	if (isset($xml->device->serviceList->service)) {
		foreach ($xml->device->serviceList->service as $service) {
			$serviceType = (string)$service->serviceType;
			$controlUrl = (string)$service->controlURL;
			$services[] = $serviceType;
			if (stripos($serviceType, 'AVTransport') !== false) {					// AVTransportサービスの場合、コントロールURLを取得
				$avTransportControlUrl = rtrim($baseUrl, "/") . "/" . ltrim($controlUrl, "/");
				}
			elseif (stripos($serviceType, 'ConnectionManager') !== false) {
 				$connectionManagerControlUrl = rtrim($baseUrl, "/") . "/" . ltrim($controlUrl, "/");
				}
			}
		}
	$isRenderer = in_array('urn:schemas-upnp-org:service:AVTransport:1', $services) ||				// Renderer判定
				  in_array('urn:schemas-upnp-org:service:RenderingControl:1', $services) ||
				  ($deviceType == 'urn:schemas-denon-com:device:AiosDevice:1');						// AIOSDeviceのRenderer検出(例外)
	$isContentDirectory = in_array('urn:schemas-upnp-org:service:ContentDirectory:1', $services) ||	// Content判定
						  in_array('urn:schemas-upnp-org:service:ContentDirectory:2', $services);	// Diga検出(例外)

	if (($isRenderer)&&($avTransportControlUrl == null)) {							// Denon/Marantz AIOSDeviceの場合更にチェックする
		if (isset($xml->device->deviceList->device)) {								// RendererでControlURL無い場合，AIOSDeviceをチェック
			foreach ($xml->device->deviceList->device as $device) {
				if (isset($device->serviceList->service)) {
					foreach ($device->serviceList->service as $service) {
						$serviceType = (string)$service->serviceType;
						$controlUrl = (string)$service->controlURL;
						$services[] = $serviceType;
						if (stripos($serviceType, 'AVTransport') !== false) {		// AVTransportのコントロールURLを取得
							$avTransportControlUrl = rtrim($baseUrl, "/") . "/" . ltrim($controlUrl, "/");
							$modelName=$modelName."(HEOS)";							// Renderer種の判定で"HEOS"を使用する
							}
						}
					}
			 	}
			}
		}
	$icons = array();;
	if (isset($xml->device->iconList->icon)) {
		foreach ($xml->device->iconList->icon as $icon) {
			$iconInfo = array(
				'url' => rtrim($baseUrl,"/")."/".trim((string)$icon->url,"/"),
				'width' => (int)$icon->width,
				'height' => (int)$icon->height,
				'depth' => (int)$icon->depth,
				'mimetype' => (string)$icon->mimetype,
				);
       $icons[] = $iconInfo; // アイコン情報を配列に追加
			}
		}

  // 48ピクセルに最も近いアイコンを選択
    $closestIcon = null;
    $closestDistance = PHP_INT_MAX;
    foreach ($icons as $icon) {
        $distance = abs($icon['width'] - 48) + abs($icon['height'] - 48);
        if ($distance < $closestDistance) {
            $closestDistance = $distance;
            $closestIcon = $icon;
        }
    }
	if(!isset($iconInfo)) {	$iconInfo=array(); }
	return array(											// DeviceType isRenderer:True=Renderer ,isContentDirectory:True:ContentDevice
		'deviceType'   => $deviceType,
		'isRenderer'   => $isRenderer,
		'isContentDirectory' => $isContentDirectory,
		'baseUrl'      => $baseUrl,
		'friendlyName' => $friendlyName,
		'manufacturer' => $manufacturer,
		'modelName'    => $modelName,
		'iconsary'     => $iconInfo,
		'icons'        => $closestIcon, // 48Pixcelに最も近いIcon情報
//		'icons'        => $iconInfo,	// Small Icon
		'services'     => $services,
		'avTransportControlUrl' => $avTransportControlUrl,		// AVTransportのコントロールURL
		'connectionManagerUrl'  => $connectionManagerControlUrl,	// ConnectionManagerのコントロールURL
		);
	}

/* ----------------------------------------------------------------------------
	getDeviceSocket($ssdpUrl,$errstr) : SSDPRequest Send Sock,Get ContentDirectoryServiceURL
	$ssdpUrl  : i  : SSDP CastのDevice Location (url)
	$errstr   : io : ErrorString (@return:False時有効)
	@return   : o  : Array:$xml , False:XML作成失敗時

  ※置き換える
	$response = file_get_contents($ssdpUrl);

----------------------------------------------------------------------------- */
function getDeviceSocket($ssdpUrl,&$errstr) {
	global $G_TIMEOUT_SSDPREQ;

	$urlParts = parse_url($ssdpUrl);						// URL Parts
	$host = $urlParts['host'];
	$port = isset($urlParts['port']) ? $urlParts['port'] : 80;
	$path = isset($urlParts['path']) ? $urlParts['path'] : '/';

	if(strpos($ssdpUrl,'udhisapi.dll') !== false){			// WMSの検出:udhisapi.dllはWindowsMediaServerのEndPoint
		$path=$ssdpUrl;
		}
	$headerStr = "GET $path HTTP/1.1\r\n";					// HeaderSetting
	$headerStr .= "Host: $host\r\n";
	$headerStr .= "Connection: close\r\n\r\n";

	$previousErrorLevel = error_reporting(0);				// fsockopenのエラーは取っているので抑制する
    $socket = fsockopen($host, $port, $errno, $errstr, $G_TIMEOUT_SSDPREQ);
	error_reporting($previousErrorLevel);
//$now = date("H:i:s");file_put_contents("./log.txt",$now." ","url[$ssdpUrl]header[$headerStr]\n", FILE_APPEND);
	if (!$socket) {
		 $errstr="Device Diable : ($errstr:$errno)";		// SockOpen Error
		 return(false);
		 }
	fwrite($socket, $headerStr);							// Request Send

	$response = '';											// Get Response
	while (!feof($socket)) {
		$response .= fgets($socket, 128);
		}
	fclose($socket);

	$parts = explode("\r\n\r\n", $response, 2);				// Headerと本文を分離
	if (count($parts) !== 2) {
		$errstr="Error: SSDP Invalid response";				// 無効な応答
		return(false);
		}
    $body = $parts[1];

	if (preg_match('/<\?xml.*<\/root>/s', $body, $matches)) {	// <\?xml～>(有効XML)</root>間を抽出
	     $xmlString = $matches[0]; // 抽出したXMLデータ
		 }
	else {
		$errstr="Error: SSDP Invalid response(No XML Target)";	// XML対象がない
		 return(false);
		 }
	$xmlString = preg_replace('/<control[^>]*URL>/', '<controlURL>', $xmlString);		// <control xxx URL>を<controlURL>に置換:BR-4KZ600(ｽﾏﾎdeﾚｸﾞｻﾞ)対応
	$xml = simplexml_load_string($xmlString);				// Make XML
	if ($xml === false) {
//		print "URL[".$ssdpUrl."]<BR>". $xmlString."<BR>";die("XML Test");	// Test用XML展開できないときに見る
		$errstr="Error: SSDP Invalid response(XML Sring)";	// XML変換Error
   		return(false);
		}
	return($xml);
	}

/* ****************************************************************************
	i/F UPnP Browse
	 getDeviceDescription()    : SSDPRequest Send Sock
	 browseContent()           : ContentDirectoryService , Send BrowseRequest
	 browseContentSock()       : Sock i/F ContentDirectoryService BrowseRequest 
	 browseContentArray()      : From XML , Browse Content to Array
	 bbrowseSOAPRequest()      : Snenb ContentDirectory Service
	 browseContentParse()      : XML browse content to Music to Array&Parse
	 browseContentMargeMarge() : browseContent Directory/File Marge
	 browseErrorParse()        : SOAP Response Parse ErrorCode
	※CommonFunctionは概要除く
***************************************************************************** */
/* ----------------------------------------------------------------------------
	getDeviceDescription($ssdpUrl) : SSDPRequest Send Sock,ContentDirectoryServiceURLGet <ContentDirectory>
	$ssdpUrl : i  : SSDP CastのDevice Location
	@return  : o  : ContentDirectory Servcie URL
	                False : NULL
	※SSDPリクエストを送信しデバイスの説明XMLを取得してContentDirectoryサービスのURLを取得
----------------------------------------------------------------------------- */
function getDeviceDescription($ssdpUrl) {
	$deviceDescription=getDeviceSocket($ssdpUrl,$errstr);
	if($deviceDescription==false){							// SocketError & XML変換Error
		die($errstr);
		}
	$controlURL=null;
	$baseUrl = parse_url($ssdpUrl);							// ベース URL を取得 (Host Name+Port)
	$baseUrl = $baseUrl['scheme'] . '://' . $baseUrl['host'] . ':' . $baseUrl['port'];
	if(strpos($ssdpUrl,'udhisapi.dll') !== false){			// WMSの検出:udhisapi.dllはWindowsMediaServerのEndPoint
		foreach ($deviceDescription->serviceList->service as $service) {					// <deviceDescription><device>がない形式
			if ($service->serviceType == 'urn:upnp-org:serviceId:ContentDirectory') {		// WMSの検出:WindowsMediaPlayer
				return $baseUrl . (string)$service->controlURL;
				}
			}
		}
	foreach ($deviceDescription->device->serviceList->service as $service) {
		if ($service->serviceType == 'urn:schemas-upnp-org:service:ContentDirectory:1') {			// 一般的なもの
			return $baseUrl . (string)$service->controlURL;
			}
		if ($service->serviceType == 'urn:schemas-upnp-org:service:ContentDirectory:2') {			// Diga対応
			return (string)$service->controlURL;
			}
		}
	if($controlURL==""){
		die('Error: DMS Control Not Found');
		return(rtrim($baseUrl,"/") .'/'.'dms/control/ContentDirectory');
		}
	return($controlURL);
	}

/* ----------------------------------------------------------------------------
	browseContent($DirectoryUrl,$objectId,$StartingIndex) : ContentDirectoryService BrowseRequest送信
	$DirectoryUrl : i  : ContentDirectory Servcie URL (return:getContentDirectoryUrl)
	$objectId     : i  : 先頭:0 , 次以降階層(下げる階層)
	$StartingIndex: i  : ContentDirectoryの取得Index(この位置から取得)
	@return       : o  : XML browse content
	※DIGAが複数Responseに分けている
----------------------------------------------------------------------------- */
function browseContent($DirectoryUrl, $objectId = '0',$StartingIndex='0') {
	$soapRequest = '<?xml version="1.0" encoding="utf-8"?>' .
				   '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" ' .
				   's:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' .
				   '<s:Body>' .
				   '<u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">' .
				   '<ObjectID>' . $objectId . '</ObjectID>' .
				   '<BrowseFlag>BrowseDirectChildren</BrowseFlag>' .
				   '<Filter>*</Filter>' .
				   '<StartingIndex>' . $StartingIndex . '</StartingIndex>' .
				   '<RequestedCount>0</RequestedCount>' .
				   '<SortCriteria></SortCriteria>' .
				   '</u:Browse>' .
				   '</s:Body>' .
				   '</s:Envelope>';

	$headers = [
		'Content-Type: text/xml; charset="utf-8"',
		'SOAPAction: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"'
		];
	$response=browseContentSock($DirectoryUrl,$soapRequest,$headers);
//	$response=browseContentFileget($DirectoryUrl,$soapRequest,$headers);		// file_get_contents()でテストする
	if ($response === FALSE) {
		$response='Error: Unable to browse content.';
		}
    return($response);
	}

/* ----------------------------------------------------------------------------
	browseContentSock($DirectoryUrl,$soapRequest,$headers,$needsclose) : ContentDirectoryService BrowseRequest Sock
	$DirectoryUrl: i  : ContentDirectory Servcie URL (return:getContentDirectoryUrl)
	$soapRequest : i  : XML SOAP Request
	$headers     : i  : Headers Arreay
	$needsclose  : i  : "y":Connection Close SsoapRequestに含む ,"n":Connection Close SsoapRequestに含まない

	$context = stream_context_create([
			'http' => [
			'method' => 'POST',
			'header' => implode("\r\n", $headers),
			'content' => $soapRequest
			]
		]);
	$response = file_get_contents($DirectoryUrl, false, $context);
	を置き換える，@file_get_contentsでテスト後置き換えることができる
	$response = browseContentSock($DirectoryUrl, $soapRequest, $headers = array())
----------------------------------------------------------------------------- */
function browseContentSock($DirectoryUrl, $soapRequest, $headers = array(),$needsclose="y") {
	global $G_TIMEOUT_CONTENTS,$G_TIMEOUT_BROWSE;

	$urlParts = parse_url($DirectoryUrl);						// URLの解析
	$host = $urlParts['host'];
	$port = isset($urlParts['port']) ? $urlParts['port'] : 80;
	$path = $urlParts['path'];

	if(strpos($DirectoryUrl,'udhisapi.dll') !== false){			// WMSの検出:udhisapi.dllはWindowsMediaServerのEndPoint
		$path=$DirectoryUrl;
		}
	$headerStr = "POST $path HTTP/1.1\r\n";								// Headerの設定
	$headerStr .= "Host: $host\r\n";
	foreach ($headers as $header) {
		$headerStr .= "$header\r\n";
		}
	$headerStr .= "Content-Length: " . strlen($soapRequest) . "\r\n";
	if($needsclose !=="n") {											// Connection: closeで失敗するReqestがある
		$headerStr .= "Connection: close\r\n\r\n";						// Connection: Keep-Alive\r\n\r\n";にするとConnectionを持続する
		}
//	$headerStr .= "Connection: keep-alive\r\n";							// Connectionを維持(動作しないServerがある:REGZA)
	$socket = fsockopen($host, $port, $errno, $errstr, $G_TIMEOUT_BROWSE);
//	$now = date("H:i:s");file_put_contents("./log.txt",$now." ". "url[$DirectoryUrl]header[$headerStr]xml[$soapRequest]\n", FILE_APPEND);
	if (!$socket) {
		 return("Error: $errstr ($errno)");
		 }
	 stream_set_blocking($socket, false);					// Action前にBufferClear
	 stream_set_timeout($socket, 0);						// Timeoutを待たない
	 $remainingData = stream_get_contents($socket);
	 stream_set_blocking($socket, true);					// Action前にBufferClear End

	 $socket_data_arr = ["time_out" => false];
	 stream_set_timeout($socket, $G_TIMEOUT_CONTENTS);					// Read Timeoutの設定
	 if (fwrite($socket, $headerStr . $soapRequest) === FALSE) {;		// Request Send
		fclose($socket);
		return("Error: SoapRequest Flase(fwrite)");
		}
	 $response = '';
	 while (!feof($socket) && !$socket_data_arr["time_out"]) {
		$response .= fgets($socket, 2048);
		$info = stream_get_meta_data($socket);
		if ($info['timed_out']) {
			$socket_data_arr["time_out"] = true;
			break;
			}
		}
	fclose($socket);
	if ($socket_data_arr["time_out"]) {
		 return("Error: Connection Timedout");
		 }
	list($header, $body) = explode("\r\n\r\n", $response, 2);			// ヘッダとボディを分離 chunked 対応追加 -----------
	if (stripos($header, 'Transfer-Encoding: chunked') !== false) {		// chunked ならデコード
		$body = browseContent_chunkeddecode($body);
		}
	return($body);														// ボディ（XML 部分のみ）を返す
	}

/*
	browseContent_chunkeddecode($chunk) : Transfer-Encoding: chunked の本文データをデコード
	$chunk   : i  : ヘッダ分離後の "chunked エンコードされた本文" を渡す。
	@return  : o  : string chunk を展開した生のボディデータ。
  ※Responseの形式
	HTTP/1.1 200 OK
	Ext: 
	Content-Type: text/xml; charset="utf-8"
	SERVER: Posix/200809.0 UPnP/1.1 ohNet/1.0
	Transfer-Encoding: chunked  ← Chunk Decodeを行う
	Connection: close
*/
function browseContent_chunkeddecode($chunk) {
	$result = '';
	while (true) {
		$pos = strpos($chunk, "\r\n");
		if ($pos === false) { break; }
		$lenHex = substr($chunk, 0, $pos);
		$len = hexdec($lenHex);
		if ($len === 0) { break; }
		$result .= substr($chunk, $pos + 2, $len);
		$chunk = substr($chunk, $pos + 2 + $len + 2);		// 次のチャンクへ (size + CRLF + body + CRLF)
		}
	return $result;
	}

/*
	browseContentFileget($DirectoryUrl,$soapRequest,$headers) : ContentDirectoryService BrowseRequest file_get_contents
	$DirectoryUrl: i  : ContentDirectory Servcie URL (return:getContentDirectoryUrl)
	$soapRequest : i  : XML SOAP Request
	$headers     : i  : $context array()
	@return      : i  : string: Success:Response Strin / null:False
	※Header&SOAPRequestが不明でsendSoapSockが使用できない時file_get_contentsを使用
	  sendSoapSockと同じ引数として互換
*/
function browseContentFileget($DirectoryUrl,$soapRequest,$headers){
	$context = stream_context_create([
			'http' => [
			'method' => 'POST',
			'header' => implode("\r\n", $headers),
			'content' => $soapRequest
			]
		]);
	$response = file_get_contents($DirectoryUrl, false, $context);
//	$now = date("H:i:s");file_put_contents("./log.txt",$now." ". "url[$DirectoryUrl]header[$headerStr]xml[$soapRequest]\n", FILE_APPEND);
	return($response);
	}

/* ----------------------------------------------------------------------------
	browseContentArray($response) : XML browse content to Array
	$response : i  : browseContent(): $response
	@return  : o  : XML browse content Array
	                arrqy[][id]:ID  / [][parentID]:親のID [][title]Title / [][ArtURI]ArtURI / [][class] object.containerなど
	※ $response
	   <s:Envelope xmlns:s="http://schemas～
       <s:Body>
	    ～
     （ <Result>～</Result>が有効だがentity_decodeしないと使用できない）
	   <Result>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;
	       ?&gt;&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:
	    ～
      </Result>
	    ～
      </s:Envelope>
----------------------------------------------------------------------------- */
function browseContentArray($response) {
	preg_match('/<Result.*?>(.*?)<\/Result>/s', $response, $matches);		// preg_matchで<Result>タグの内容を抽出
	if (empty($matches[1])) {
		return("Error: BrowseContentResponse(XMLBrowseContent),Failed to extract element");
		}
	$resultDecoded = htmlspecialchars_decode($matches[1]);					 // HTMLエンティティのデコード
	$xml = simplexml_load_string($resultDecoded);					// simplexml_load_stringでXMLをオブジェクトに変換
	if(!$xml) {
		preg_match('/<DIDL-Lite.*?>(.*?)<\/DIDL-Lite>/s', $resultDecoded, $matches);	// <DIDL-Lite>タグの内容を抽出(ｽﾏﾎdeﾚｸﾞｻﾞゴミ対策)
		$resultDecoded=$matches[0];
		$xml = simplexml_load_string($resultDecoded);
		}
	$xml->registerXPathNamespace('default', 'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/');	// 名前空間を登録
	$xml->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/');
	$xml->registerXPathNamespace('upnp', 'urn:schemas-upnp-org:metadata-1-0/upnp/');

	$containers = [];												// 各containerを配列に格納
	foreach ($xml->xpath('//default:container') as $key => $container) {
		$containers[$key]["id"]       = (string) $container['id'];
		$containers[$key]["parentID"] = (string) $container['parentID'];
		$containers[$key]["childCount"]=(string) $container['childCount'];
		$containers[$key]["title"]    = (string) $container->xpath('dc:title')[0];
		$containers[$key]["title"]   =str_replace(["\r", "\n"], "",$containers[$key]["title"]);		// Item中に"\r","\n"があるので''にする
		$containers[$key]["class"]    = (string) $container->xpath('upnp:class')[0];
		$containers[$key]["ArtURI"]   = (string) $container->xpath('upnp:albumArtURI')[0];
		$containers[$key]["date"]     = isset($container->xpath('dc:date')[0]) ? (string) $container->xpath('dc:date')[0] : null;	// DLNA:contentsにDateはない
		}
	foreach ($xml->xpath('//default:item') as $key => $item) {			// <item> 要素の処理（<res>を含む要素）
		$resElement = $item->xpath('default:res');
		if (!empty($resElement)) {
			$protocolInfo = (string) $resElement[0]['protocolInfo'];	// protocolInfo属性を取得
			$containers[$key]["protocolInfo"] = $protocolInfo;
			}
		}
	return($containers);
	}

/* ----------------------------------------------------------------------------
	browseSOAPRequest($DirectoryUrl,$request,$needsclose) : ContentDirectoryサービスにリクエストを送信
	$DirectoryUrl: i  : ContentDirectory Servcie URL (return:getContentDirectoryUrl)
	$request     : i  : ClearError,Reset,GetStatus
	$needsclose  : i  : RequestにCloseが必要時 "y" 不要時は"n"(Default)
	@return      : o  : XML browse content
----------------------------------------------------------------------------- */
function browseSOAPRequest($DirectoryUrl,$request,$needsclose="n") {
	$soapRequest = '<?xml version="1.0" encoding="utf-8"?>' .
					'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'.
					'	<s:Body>'.
					'	<u:'.$request.' xmlns:u="urn:schemas-upnp-org:service:DeviceManagement:1">'.
					'	</u:'.$request.'>'.
					'	</s:Body>'.
					'</s:Envelope>';
	$headers = [
		'Content-Type: text/xml; charset="utf-8"',
		'SOAPAction: "urn:schemas-upnp-org:service:ContentDirectory:1'.$request.'"'
		];
	$response=browseContentSock($DirectoryUrl,$soapRequest,$headers,$needsclose);
//	$response=browseContentFileget($DirectoryUrl,$soapRequest,$headers);		// file_get_contents()でテストする
    return($response);
	}

/* ----------------------------------------------------------------------------
	browseContentParse($response) : XML browse content to Music to Array&Parse
	$response : i  : browseContent(): $response
	@return   : o  : XML browse content Music Array
	※ $response
	   <s:Envelope xmlns:s="http://schemas～
       <s:Body>
	    ～
     （ <Result>～</Result>が有効だがentity_decodeしないと使用できない）
	   <Result>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;
	       ?&gt;&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:
	    ～
      </Result>
	    ～
      </s:Envelope>
----------------------------------------------------------------------------- */
function browseContentParse($response) {
	preg_match('/<Result.*?>(.*?)<\/Result>/s', $response, $matches);		// preg_matchで<Result>タグの内容を抽出
	if (empty($matches[1])) {
		return("Error: BrowseContentResponse(XMLBrowseMedia),Failed to extract element");
		}
	$resultDecoded = htmlspecialchars_decode($matches[1]);					 // HTMLエンティティのデコード
	$xml = simplexml_load_string($resultDecoded);
	if(!$xml) {
		preg_match('/<DIDL-Lite.*?>(.*?)<\/DIDL-Lite>/s', $resultDecoded, $matches);	// <DIDL-Lite>タグの内容を抽出(ｽﾏﾎdeﾚｸﾞｻﾞゴミ対策)
		$resultDecoded=$matches[0];
		$xml = simplexml_load_string($resultDecoded);
		}
	$namespaces = $xml->getNamespaces(true);						// 名前空間を登録
	$didl = $xml->children($namespaces['DIDL-Lite']);
	$containers = [];
	if (!$didl) {
		return($containers);		// QobuzはFile以前の$responseが異なる'DIDL-Lite'名前空間を登録分解できない
    	}
	foreach ($didl as $element) {
		if ($element->getName() === "item") {
			$childrenDc = $element->children($namespaces['dc']);
			$childrenUpnp = $element->children($namespaces['upnp']);
			$childrenDlna = $element->children($namespaces['dlna']);
			$containers[] = [										// 各要素を配列に格納
				"id" => (string)$element['id'],
				"parentID" => (string)$element['parentID'],
				"title" => (string)$childrenDc->title,
				"artist" => (string)$childrenUpnp->artist,
				"album" => (string)$childrenUpnp->album,
				"originalTrackNumber" => (string)$childrenUpnp->originalTrackNumber,
				"ArtURI" => (string)$childrenUpnp->albumArtURI,
				"genre" => (string)$childrenUpnp->genre,
				"class" => (string)$childrenUpnp->class,
				"date" => (string)$childrenDc->date,
				"url" => (string)$element->res,
				"protocolInfo" => (string)$element->res['protocolInfo'],
				"duration"  => (string) $element->res['duration'],
				];
			}
		}
	foreach ($containers as &$val) {
		$val=str_replace(["\n","\r","\a"],"",$val);		// minidlna:"\a"が含まれるケースがある(minidlna改行のバグ?)
		}
	return($containers);
	}

/* ----------------------------------------------------------------------------
	browseContentMargeMarge($contns,$files) : browseContent Marge
	$contns : i  : browseContentArray,Response:Contns Array
	$files  : i  : browseContentParse,Response:Files Array
	@return : o  : Marge Array
	※ Contns&FilesをMarge Return Array["Mode"]:"contns","files"を追加。
----------------------------------------------------------------------------- */
function browseContentMarge($contns,$files) {
	global $G_IMAGEEXT;

	$aryresult=array();
	$contns_key=array_keys($contns);
	$i=0;
	foreach ($contns as $key => $ary) {
		$contns_key=array_keys($ary);
		$aryresult[$i]["Mode"]="contns";
		foreach ($contns_key as $akey) {
			$aryresult[$i][$akey]=$ary[$akey];	// 全てのKeyをSort対象Sort後に従い作成
			}
		$i=$i+1;
		}
	foreach ($files as $key => $ary) {
		$contns_key=array_keys($ary);
		$aryresult[$i]["Mode"]="files";
		foreach ($contns_key as $akey) {
			$aryresult[$i][$akey]=$ary[$akey];	// 全てのKeyをSort対象Sort後に従い作成
			}
		if((isset($aryresult[$i]["url"]))&&((!isset($aryresult[$i]["ArtURI"]))||((isset($aryresult[$i]["ArtURI"]))&&($aryresult[$i]["ArtURI"]=="")))) {
			$ext=substr($aryresult[$i]["url"], strrpos($aryresult[$i]["url"], '.') + 1);
			$ext=strtolower($ext);
			if (in_array($ext,$G_IMAGEEXT)) {	// 拡張子が画像のとき，ArtURLがなければURLを使用する
				$aryresult[$i]["ArtURI"]=$aryresult[$i]["url"];
				}
			}
		$i=$i+1;
		}
	return($aryresult);
	}

/* ----------------------------------------------------------------------------
	browseErrorParse($response) : SOAP ResponseにErrorCodeがあればParse
	$response: i  : SOAP Response
	@return  : o  : ErrorCode:Message
----------------------------------------------------------------------------- */
function browseErrorParse($response) {
	$str="";
	preg_match('/<errorCode.*?>(.*?)<\/errorCode>/s', $response, $matches);
	if (isset($matches[1])) {
		$str=$matches[1];
		}
	preg_match('/<errorDescription.*?>(.*?)<\/errorDescription>/s', $response, $matches);
	if (isset($matches[1])) {
		if ($str !=="") {
			 $str=$str.":".$matches[1];
			 }
		else {
			 $str=$matches[1];
			 }
		}
	return($str);
	}

/* ----------------------------------------------------------------------------
	ArgMimeTypeParse($protocalinfo,$unknown): MieType Purge
	$protocalinfo: i  : $protocalinfo etc:"http-get:*:audio/x-flac" , MimeType String
	$unknown     : i  : Not Compare , Return String
	@return      : o  : [0]FileExtension 不明:unknown
	                    [1]Type image/video/Audio
----------------------------------------------------------------------------- */
function ArgMimeTypeParse($protocalinfo,$unknown="unknown") {
	$parts = explode(':', $protocalinfo);
	$mimeType = isset($parts[2]) ? $parts[2] : null;
	$mimeToExtensionMap = [
		'audio/mpeg' => 'mp3',
		'audio/x-flac' => 'flac',
		'audio/x-dsd' => 'dsf',
		'audio/wav' => 'wav',
		'audio/aac' => 'aac',
		'audio/mp4' => 'm4a',
		'video/mp4' => 'mp4',
		'image/jpeg' => 'jpg',
		'image/png' => 'png',
		];
	$fileType = explode('/', $mimeType)[0];			    // ファイル種類（audio/image/video）を抽出
    $fileExtension = isset($mimeToExtensionMap[$mimeType]) ? $mimeToExtensionMap[$mimeType] : $unknown;	// 拡張子を推定
	$result[0]=$fileExtension;
	$result[1]=explode('/',$mimeType)[0];
	return($result);
	}

/*
	createCurrentURIMetaData($url,$parentID,$objectId) : メディア情報を取得してCurrentURIMetaDataを作成する関数
	$url     : i  : URL(Top)
	$parentID: i  : $objectIdへのParentID
	$objectId: i  : Content ID
	@return  : o  : CurrentURIMetaData (XML形式) False:""(再生はできるのでセットしない)

※ $MetaData=createCurrentURIMetaData($url,$id);
	$params = array(
		"InstanceID" => "0",
		"CurrentURI" => $mediaurl, 
		"CurrentURIMetaData" => $MetaData, : この部分を作成
		);
	$response = sendUpnpRequest($controlUrl,$service,$action,$params);
*/
function createCurrentURIMetaData($url,$parentID,$objectId) {
	$DirectoryUrl = getDeviceDescription($url);
	if ($deviceDescription === FALSE) {
		die('Error: Unable to parse device description.');
		}
	if ($id !==""){
		 $response = browseContent($DirectoryUrl,$parentID);
		 }
	else {
		 $response = browseContent($DirectoryUrl);
		 }
	if ($response === FALSE) {
		die('Error: Unable to parse content list.');
		}
	$containers=browseContentArray($response);
	$contafiles=browseContentParse($response);
	$containers=browseContentMarge($containers,$contafiles);

	$contentInfo = [];
	foreach ($containers as $item) {
		if ($item['id'] ==$objectId) {
			$contentInfo['title']=$item['title'];
			$contentInfo['artist']=$item['artist'];
			$contentInfo['album']=$item['album'];
			$contentInfo['originalTrackNumber']=$item['originalTrackNumber'];
			$contentInfo['type'] = $item['class'];
			$contentInfo['protocolInfo']=rtrim($item['protocolInfo']," ");
			$contentInfo['url']=$item['url'];
			$contentInfo['duration']=$item['duration'];
			if (isset($item['ArtURI'])) {
				$contentInfo['ArtURI']=$item['ArtURI'];
				}
			break;
			}
		}
// メタ情報をXML形式に変換
// <item restricted="1" : メディアアイテムコピー不可、移動不可、または他の操作が制限されている場合に使用
	$meta_albumArt="";
	if (isset($contentInfo['ArtURI'])) {
		$meta_albumArt='<upnp:albumArtURI>' .htmlspecialchars($contentInfo['ArtURI']) . '</upnp:albumArtURI>';
		}
	$metaDataXml = '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" ' . 	// タイトルは表示されないので追加
				   'xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" ' .		// タイトルは表示されないので追加
				   'xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" ' . 
				   'xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/">' . 
				   '<item id="' . htmlspecialchars($objectId) . '" parentID="0" restricted="1" searchable="0">' .		// Captreから restricted="1" searchable="0" を対応
				   '<dc:title>' . htmlspecialchars($contentInfo['title']) . '</dc:title>' .
				   '<upnp:class>' . htmlspecialchars($contentInfo['type']) . '</upnp:class>' .
				   '<upnp:album>' . htmlspecialchars($contentInfo['album'] ?? 'Unknown album').  '</upnp:album>' .
				   '<upnp:originalTrackNumber>'. htmlspecialchars($contentInfo['originalTrackNumber']) .'</upnp:originalTrackNumber>' .
					$meta_albumArt.
				   '<res protocolInfo="' . htmlspecialchars($contentInfo['protocolInfo']) . '" duration="' . htmlspecialchars($contentInfo['duration']) . '">' .
										   htmlspecialchars($contentInfo['url']) .'</res>' .		// ProtocolInfo & URL & 再生時間 をDMRに投入
//				   '<res>' . htmlspecialchars($contentInfo['url']) .'</res>' .						// Test時,URLのみDMRに投入
			   '<dc:creator>' . htmlspecialchars($contentInfo['artist'] ?? 'Unknown Artist') . '</dc:creator>' .
			   '<upnp:artist>' . htmlspecialchars($contentInfo['artist'] ?? 'Unknown Artist') . '</upnp:artist>' .
			   '</item>' .
			   '</DIDL-Lite>';
	$metaDataXml = htmlspecialchars($metaDataXml);						// URLエンコード
//	$now = date("H:i:s");file_put_contents('./log.txt',$now." ".$metaDataXml."\n",FILE_APPEND);
	return $metaDataXml;
	}


/* ****************************************************************************
	Renderer UPnP Request
	 sendUpnpRequest() : Renderer SOAP Request
	 sendSoapSock()    : SOAP Request Sock
	 sendSoapFileget() : SOAP Request Used URL(sendSoapSockと同機能)
	※Renderer RequestはPost参照
***************************************************************************** */
/* ----------------------------------------------------------------------------
	sendUpnpRequest($controlUrl,$service,$action,$params,$modename) : SOAP Request送信する
	$controlUrl: i  : UPnP Deviceの制御URL
	$service   : i  : ServiceName（例: "AVTransport"）
	$action    : i  : ActionName（例: "SetAVTransportURI"）
	$params    : i  : Array Action 必要なParameter (key: ParameterName,ParameterValue)
	$modename  : i  : $controlUrl:Render ModeName

	@return    : i  : string: Success:Response Strin / null:False

※ modeName=Render+"|"+modeName; modeName:機種名だけになってしまうので,Render(FriendlyName)+"|"+modeName
   SoftRenderは，設定でFriendlyNameを決められるが，H/WRenderは機種固定,詳細はmodeNameと考える。
   先々は外部から機種例外Special設定をできるようにするか検討。現在は例外がどういう形か不明。

  TCP/IP Post Chapcher : tsharkは gz圧縮でもTextできる。
※ # tshark -i eno1 -f 'tcp port 2870' -V > p160.txt
----------------------------------------------------------------------------- */
function sendUpnpRequest($controlUrl,$service,$action,$params,$modename="") {
	$soapitem = "";
	foreach ($params as $key => $value) {
		$soapitem .= "<$key>$value</$key>\n";
		}
// --------------------------------------------------------------------------
// Marantz の例外:Denonも同じと思うが機種確認はできていない
// Action : SetAVTransportURI : <u:SetAVTransportURIが必要
	if((stripos($modename,'HEOS') !== false)&&($action=="SetAVTransportURI")) {
		if(isset($params['"CurrentURIMetaData"'])) {
			unset($params['"CurrentURIMetaData"']);		// MetaのセットでFalseになるのでUnlink(解明後Set)
			}
		$soapitem = "";
		foreach ($params as $key => $value) {
			$soapitem .= "<$key>$value</$key>\n";
			}
$soapRequest = <<<EOD
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <soap:Body>
    <u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
        {$soapitem}
    </u:SetAVTransportURI>
  </soap:Body>
</soap:Envelope>
EOD;	//<--<?コメント調整>-->
		$headers = array(
			"SOAPAction: \"urn:schemas-upnp-org:service:".$service.":1#".$action."\"",
			);
		 }
// --------------------------------------------------------------------------
// Pioneer BDPの例外:機種確認はできていない 
// <soap:空間 <sでは false XMLに癖がある
	else if((stripos($modename,'Pioneer') !== false)&&(stripos($modename,'|BDP') !== false)) {
		 $soapRequest = <<<EOD
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <soap:Body>
    <u:{$action} xmlns:u="urn:schemas-upnp-org:service:{$service}:1">
        {$soapitem}
    </u:$action>
  </soap:Body>
</soap:Envelope>
EOD;	//<--<?コメント調整>-->
		 $headers = array(
			"SOAPAction: \"urn:schemas-upnp-org:service:".$service.":1#".$action."\"",
			);
// file_get_contents()は必ず成功している(対応できれば削除) ///////////////////
		 sendSoapFileget($controlUrl, $soapRequest, $headers);
		 return($response);
// 原因がわかるまで //////////////////////////////////////////////////////////
		 }
	else {
// --------------------------------------------------------------------------
// Standard Request
// UpMpdCli / REGZA 旧リリース で確認
		 $soapRequest = <<<EOD
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <s:Body>
    <u:{$action} xmlns:u="urn:schemas-upnp-org:service:{$service}:1">
        {$soapitem}
    </u:$action>
  </s:Body>
</s:Envelope>
EOD;	//<--<?コメント調整>-->
		$headers = array(
			"SOAPAction: \"urn:schemas-upnp-org:service:".$service.":1#".$action."\"",
			);
		}
// --------------------------------------------------------------------------
// 例外含めて,$soapRequestを作成後，OptionPara作成
	$response = sendSoapSock($controlUrl, $soapRequest, $headers);
	if ($response === false) {			        // エラーログを出力
		$error = error_get_last();
//		$now = date("H:i:s");file_put_contents('./log.txt',$now.":".$error['message']."\n",FILE_APPEND);
		error_log("UPnP Request failed: " . $error['message']);
		return null;
		}
    error_log("UPnP Response: " . $response);	// レスポンスの内容をログに記録
    return($response);
	}

/* ----------------------------------------------------------------------------
	sendSoapSock($url,$xml,$headers) : SOAP Request Sock
	$Url       : i  : UPnP Deviceの制御URL(ControlURL)
	$xml       : i  : $soapRequest
	$headers   : i  : $context array()
	@return    : o  : string: Success:Response Strin / null:False

  ※以下を置き換える。
	$options = array(
			'http' => array(
			'method'  => 'POST',
			'header'  => implode("\r\n", $headers),
			'content' => $soapRequest,
			),
		);
	$context = stream_context_create($options);
	$response = @file_get_contents($controlUrl, false, $context);
  を置き換える，@file_get_contentsでテスト後置き換えることができる
	$response = sendSoapSock($controlUrl, $soapRequest, $headers);
----------------------------------------------------------------------------- */
function sendSoapSock($url, $xml, $headers = array()) {
	global $G_TIMEOUT_RENDERER,$G_TIMEOUT_RENDERERV;

	$urlParts = parse_url($url);
	$host = $urlParts['host'];										// URL Parse
	$port = isset($urlParts['port']) ? $urlParts['port'] : 80;
	$path = $urlParts['path'];

	$headerStr = "POST $path HTTP/1.1\r\n";							// Header Setting
	$headerStr .= "Host: $host\r\n";
	$headerStr .= "Content-Type: text/xml; charset=utf-8\r\n";
//	$headerStr .= "Connection: close\r\n";  // 接続を閉じる指示:デバイスの接続管理が改善され、最初の接続で問題が解決されることがある
	foreach ($headers as $header) {
		$headerStr .= "$header\r\n";
		}
	$headerStr .= "Content-Length: " . strlen($xml) . "\r\n";
	$headerStr .= "Connection: close\r\n\r\n";
	$socket = fsockopen($host, $port, $errno, $errstr, $G_TIMEOUT_RENDERER);
//$now = date("H:i:s");file_put_contents("./log.txt",$now." "."url[".$url."]header[".$headerStr."]xml[".$xml."]\n",FILE_APPEND);
	if (!$socket) {
		 error_log("Error: $errstr ($errno)");
		 return(false);
		 }
	fwrite($socket, $headerStr . $xml);						// Request Send
	$response = '';$result= '';
	if ($G_TIMEOUT_RENDERERV === false) { } else {
		stream_set_timeout($socket, $G_TIMEOUT_RENDERERV);	// ReceiveTimeout
		}
	while (!feof($socket)) {
		$line = fgets($socket, 1024); 			// バッファ大きめ
		if ($line === false) { break; } 		// Read False AbEnd
		$response .= $line;
		$info = stream_get_meta_data($socket);
		if ($info['timed_out']) {
			error_log("UPnP Request timed out, partial response returned");
			$result=false;
			break;
			}
		}
	fclose($socket);
	if($result ===false) { return(false); }
	return($response);
	}

/* ----------------------------------------------------------------------------
	sendSoapFileget($url,$xml,$headers) : SOAP Request file_get_contents
	$Url       : i  : UPnP Deviceの制御URL(ControlURL)
	$xml       : i  : $soapRequest
	$headers   : i  : $context array()
	@return    : i  : string: Success:Response Strin / null:False
	                  Timeout:false
	※Header&SOAPRequestが不明でsendSoapSockが使用できない時file_get_contentsを使用
	  sendSoapSockと同じ引数として互換
----------------------------------------------------------------------------- */
function sendSoapFileget($url, $xml, $header = array()) {
	global $G_TIMEOUT_RENDERER;

	$headers = array(								// Header&HTTP Requestリクエストの構造で要求が異なる様子
				"Content-Type: text/xml; charset=utf-8",
				);
	foreach ($header as $val) {
		$headers[]=$val;
		}
	$options = array(
			'http' => array(
			'method'  => 'POST',
			'header'  => implode("\r\n", $headers),
			'content' => $xml,
			'timeout' => $G_TIMEOUT_RENDERER		//Sec ChatGPT
			),
		);
	$context = stream_context_create($options);
//$now = date("H:i:s");file_put_contents("./log.txt",$now." "."url[".$url."]context[".$context."]\n",FILE_APPEND);
	$response = @file_get_contents($url, false, $context);	// 一度成功させればどちらでも可
	return($response);
	}

/* ---------------------------------------------------------------------------
 使用例 
  $controlUrl = "http://192.168.0.122:49152/AVTransport/control"; // 制御URL
  $service = "AVTransport";      // サービス名
  $action = "SetAVTransportURI"; // アクション名

  $params = array(
    "InstanceID" => "0",
    "CurrentURI" => "http://example.com/audio.mp3",
    "CurrentURIMetaData" => "",
  );

  $response = sendUpnpRequest($controlUrl,$service,$action,$params);

予想以上にメーカ独自で送出後falseとなる。あまり多い場合は各メーカ関数化が必要

<注>
 <soapenvと<s:Envelopeは基本的に同じもの REGZAが<soapenvで動作しないのでこちらは使用しない
 setAVTransportURI:DMRで再生するURI（メディアのURL）とメタデータを設定するための標準的な方法
 多くのDLNA/UPnP対応機器は、このコマンドをサポート : Regzaはあると動作しない
 <s:Envelope <soapenvの省略形
 UpMpdCli:Qobuz:soapenvdでは動かない , PionnierBDP-160K:s:では動かない
 --------------------------------------------------------------------------------
 ※パケット確認方法
 # apt install tshark
 # tshark -i eno1 -w upnp_capture.pcap
 テキスト化
 # tshark -r capture.pcap -V
 or
 # tshark -r capture.pcap -V > {filename}
 ipで抽出
 # tshark -r capture.pcap -Y 'ip.addr == 192.168.0.64'

*/
//----- Source End --------------------------------------------------------------
?>
