« 2020年12月 | トップページ

2021年4月の1件の投稿

AFN 360の配信URL一覧の取得

AFN 360 Inertnet Radioは2020年末にPLSファイルの取得が不安定になり年明けにほぼ接続できなくなりました。
代わりにXMLによる配信URL一覧があります。

横田エアベースのXML例:https://playerservices.streamtheworld.com/api/livestream?station=AFNP_TKO&transports=http,hls&version=1.8
※上記リンクをFirefox開く場合はXMLを表示する拡張機能が必要です。

このXMLの中にはコーデックがmp3とAACによる配信URLが、それぞれ複数提示さてています。
この複数の配信URLからアトランダムに選択(負荷分散)して接続する仕組みです。

AFNP_TKOの部分の一覧
Tokyo=AFNP_TKO
Sasebo=AFNP_SBO
Okinawa=AFNP_OKN
Misawa=AFNP_MSW
Iwakuni=AFNP_IWA
Casey=AFNP_CSY
Kunsan=AFNP_KSN
Osan=AFNP_OSN
Yongsan=AFNP_YSN
Daegu=AFNP_DGU
Country=AFN_CTYP
Freedom=AFN_FREP
Fans=AFN_FAN
Gravity=AFN_GRV
Hot AC=AFN_HOTP
Joe Radio=AFN_JOEP
Legacy=AFN_LGYP
PowerTalk=AFN_PTK
The Voice=AFN_VCE

ちゅんラヂはAFNの配信URLをXMLから取得している
AFNの配信URLは結構な頻度で変更されます。ちゅんラヂはその都度、配信URLのプリセットを変更していました。
そこでXMLの配信一覧から、配信URLを取得する方法に変更しました。これで配信URLが変わっても安心です。XML自体のURLと書式が変更されたらダメですけど。

下記JavaScriptのソースコードがAFNの配信URLを取得する事例です。
ソースコード中の「xml2json.js」は先人の知恵を利用させて頂いております。
XHRでXMLのDOM Treeがそのまま戻せるのに、responseXMLをなぜ使うんだ?
いまさらPromiseの中でXHRは無いだろ、fetch使えよ!
などというツッコミどころがありますが大目にみてやってください。使うならもっとモダンに書き換えてください。
もっと具体的にどの様に使われているのかを知りたい場合はちゅんラヂPlayerのソースコードをリバースエンジニアリングしてください。

JavaScriptが読めなくても、上記XMLリンク先の横田エアベースのXML内容をじっくり見ると、何しているかが推測できると思います。

function getAfnURL(afnID) 
	return new Promise((resolve, reject) => 
		let xhr = new XMLHttpRequest();
		xhr.open('GET',`https://playerservices.streamtheworld.com/api/livestream?station=${afnID}&transports=http,hls&version=1.8`, true);
		xhr.onload = function() 
			if (xhr.readyState === xhr.DONE && xhr.status === 200) 
				//読み込んだXMLをObjectとしてメモリーに展開
				let plsDom = xhr.responseXML;			//読み込んだxml DOM
				let plsStr = xml2json(plsDom,'');		//XML DOMをJSON Stringに
				let pls = JSON.parse(plsStr);			//JSON StringをOjectに
				let mp3Urls = [];		//複数のmp3配信URLを取得する配列
				let aacUrls = [];		//複数のaac配信URLを取得する配列
				//配信リストを取得
				let mountPoints = pls.live_stream_config.mountpoints;
				mountPoints.mountpoint.forEach((mountPoint) => 
					mountPoint.servers.server.forEach((server) => 
						let codec = mountPoint["media-format"]["audio"]["@codec"];
						if (codec == "mp3") 
							//mp3配信URL ※mountSuffixは配信方式リストから有効な方式を判定するのが正しいらしいが、AFNはshoutcast-v1らしいので、固定指定
							mp3Urls.push(`https://${server.ip}/${mountPoint.mount}_SC`);	//配信URLを編集してpush
						} else 
							//HE-AACv2配信URL ※mountSuffixは配信方式リストから有効な方式を判定するのが正しいらしいが、AFNはshoutcast-v1らしいので、固定指定
							aacUrls.push(`https://${server.ip}/${mountPoint.mount}_SC`);	//配信URLを編集してpush
						}
					})
				});
				//複数ある配信ストリーミングURLリストからアットランダムに選定 ※mp3優先
				let ix = 0;
				if (mp3Urls.length > 0) 
					//mp3のストリーミング
					ix = Math.floor( Math.random() * (mp3Urls.length - 1) );
					resolve(mp3Urls[ix]);
				} else 
					if (aacUrls.length > 0) 
						//aacのストリーミング
						ix = Math.floor( Math.random() * (aacUrls.length - 1) );
						resolve(aacUrls[ix]);
					} else 
						console.log('[radio._getAfnUrl] error: Streaming URL is empty');
						reject(new DOMException('AFN streaming URL is empty ERROR', 'OperationError'));
					}
				}
			} else 
				console.log('[radio._getAfnUrl] error: Could not get xml normally');
				reject(new DOMException('AFN playlist XML read ERROR', 'OperationError'));
			}
		};
		xhr.onerror = function() 
			console.log('[radio._getAfnUrl] error:' + new Error(xhr.statusText));
			reject(new DOMException('AFN playlist loading XHR ERROR', 'OperationError'));
		};
		xhr.send();
	});
}
※Promise部分について
この事例はAFNのサーバーからXML取得の応答を待ちますので非同期関数にしています。
JavaScript以外でもモダンな言語ならPromise(またはFuture)が実装されていますので同様です。

| | コメント (0)

« 2020年12月 | トップページ