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内容をじっくり見ると、何しているかが推測できると思います。
※Promise部分について
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();
});
}
この事例はAFNのサーバーからXML取得の応答を待ちますので非同期関数にしています。
JavaScript以外でもモダンな言語ならPromise(またはFuture)が実装されていますので同様です。
最近のコメント