@@ -336,7 +336,7 @@
{{#if (get segment "0")}}
{{!-- template-lint-disable no-triple-curlies --}}
- {{{get segment "0.label"}}}{{#if segment.0.address}} {{/if}}:
+ {{{get segment "0.label"}}}{{#if (get segment "0.address")}} {{/if}}:
{{/if}}
{{!-- template-lint-disable no-triple-curlies --}}
{{{get segment "1"}}}
diff --git a/ember-cli-build.js b/ember-cli-build.js
index 6e07c80..591d202 100644
--- a/ember-cli-build.js
+++ b/ember-cli-build.js
@@ -161,11 +161,22 @@ module.exports = function (defaults) {
app.import('node_modules/ogv/dist/ogv.js');
app.import('vendor/videojs-ogvjs.js');
- app.import('node_modules/videojs-wavesurfer/node_modules/wavesurfer.js/dist/wavesurfer.js');
- app.import('node_modules/videojs-wavesurfer/node_modules/wavesurfer.js/dist/plugin/wavesurfer.timeline.js');
+ app.import(
+ 'node_modules/videojs-wavesurfer/node_modules/wavesurfer.js/dist/wavesurfer.js',
+ );
+ app.import(
+ 'node_modules/videojs-wavesurfer/node_modules/wavesurfer.js/dist/plugin/wavesurfer.timeline.js',
+ );
app.import('node_modules/videojs-wavesurfer/dist/videojs.wavesurfer.js');
app.import('node_modules/videojs-wavesurfer/dist/css/videojs.wavesurfer.css');
+ app.import(
+ 'node_modules/icecast-metadata-player/build/icecast-metadata-player-1.17.3.main.min.js',
+ );
+ app.import(
+ 'node_modules/icecast-metadata-player/build/icecast-metadata-player-1.17.3.mediasource.min.js',
+ );
+
const ogvAssets = new Funnel('node_modules/ogv/dist', {
srcDir: '/',
include: ['**/*.*'],
diff --git a/package-lock.json b/package-lock.json
index 9f391e7..493b571 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -64,6 +64,7 @@
"eslint-plugin-n": "^17.3.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-qunit": "^8.1.1",
+ "icecast-metadata-player": "^1.17.3",
"instantsearch.css": "^8.1.0",
"instantsearch.js": "^4.67.0",
"interactjs": "^1.10.27",
@@ -6165,6 +6166,12 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
+ "node_modules/@eshaz/web-worker": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@eshaz/web-worker/-/web-worker-1.2.2.tgz",
+ "integrity": "sha512-WxXiHFmD9u/owrzempiDlBB1ZYqiLnm9s6aPc8AlFQalq2tKmqdmMr9GXOupDgzXtqnBipj8Un0gkIm7Sjf8mw==",
+ "dev": true
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -12299,6 +12306,44 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
+ "node_modules/@wasm-audio-decoders/common": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/common/-/common-9.0.5.tgz",
+ "integrity": "sha512-b9JNh9sPAvn8PVIizNh9D60WkfQong/u9ea873H47u7zvVDLctxYIp2aZw9CQqXaQdk7JB3MoU5UHiseO40swg==",
+ "dev": true,
+ "dependencies": {
+ "@eshaz/web-worker": "1.2.2",
+ "simple-yenc": "^1.0.4"
+ }
+ },
+ "node_modules/@wasm-audio-decoders/flac": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/flac/-/flac-0.2.4.tgz",
+ "integrity": "sha512-bsUlwIjd5y+IAEyILCQdi8y0LocKEkZ0enA8ljDL+NVVwN+5Rv5Xkm/HcdUxnB7MtekxN2cNcTsv1zkb2aZyWg==",
+ "dev": true,
+ "dependencies": {
+ "@wasm-audio-decoders/common": "9.0.5",
+ "codec-parser": "2.4.3"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
+ "node_modules/@wasm-audio-decoders/ogg-vorbis": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/@wasm-audio-decoders/ogg-vorbis/-/ogg-vorbis-0.1.15.tgz",
+ "integrity": "sha512-skAN3NIrRzMkVouyfyq3gYT/op/K9iutMZr7kr5/9fnIaCnpYdrdbv69X8PZ6y3K2J5zy5KuGno5kzH8yGLOOg==",
+ "dev": true,
+ "dependencies": {
+ "@wasm-audio-decoders/common": "9.0.5",
+ "codec-parser": "2.4.3"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
"node_modules/@webassemblyjs/ast": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
@@ -16698,6 +16743,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/codec-parser": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/codec-parser/-/codec-parser-2.4.3.tgz",
+ "integrity": "sha512-3dAvFtdpxn4YLstqsB2ZiJXXNg7n1j7R5ONeDuk+2kBkb39PwrCRytOFHlSWA8q5jCjW3PumeMv9q37bFHsijg==",
+ "dev": true
+ },
"node_modules/collection-visit": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -35034,6 +35085,39 @@
"ms": "^2.0.0"
}
},
+ "node_modules/icecast-metadata-js": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/icecast-metadata-js/-/icecast-metadata-js-1.2.8.tgz",
+ "integrity": "sha512-de36uvcuEP7iSLvYMyAEs9sljq0LlOkO+PDqasT08xm9JhSPfNUrcRkuuSl4cV0Pwixr/KM8oC+sweBWnooYbw==",
+ "dev": true,
+ "dependencies": {
+ "codec-parser": "2.4.3"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
+ "node_modules/icecast-metadata-player": {
+ "version": "1.17.3",
+ "resolved": "https://registry.npmjs.org/icecast-metadata-player/-/icecast-metadata-player-1.17.3.tgz",
+ "integrity": "sha512-RWeasTRMpokJsT+hh9nyBLV6wyEXZBX5ExyvJeiROmog3oRy6XLjJSrXnBTP3r6RpNZn+4D9YBPv+KnwH+KN7A==",
+ "dev": true,
+ "dependencies": {
+ "@wasm-audio-decoders/flac": "0.2.4",
+ "@wasm-audio-decoders/ogg-vorbis": "0.1.15",
+ "codec-parser": "2.4.3",
+ "icecast-metadata-js": "1.2.8",
+ "mpg123-decoder": "1.0.0",
+ "mse-audio-wrapper": "1.4.14",
+ "opus-decoder": "0.7.6",
+ "synaudio": "0.3.5"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -37479,12 +37563,38 @@
"mpd-to-m3u8-json": "bin/parse.js"
}
},
+ "node_modules/mpg123-decoder": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/mpg123-decoder/-/mpg123-decoder-1.0.0.tgz",
+ "integrity": "sha512-WV+pyuMUhRqv7s8S6p/Ii4KQHdBD1pb3yaABxcKJRsNp+HQ/Y6z2iIBIaOZu0JMHPTOoICYt0REDZ7XfLu+n/g==",
+ "dev": true,
+ "dependencies": {
+ "@wasm-audio-decoders/common": "9.0.5"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/mse-audio-wrapper": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/mse-audio-wrapper/-/mse-audio-wrapper-1.4.14.tgz",
+ "integrity": "sha512-qdy5ezIS5JwA21DUJIYypGU2WtLzqJXCK9hgFoHu3ydjL/xrEt7XjsKuKPxYAiWZVOaBXjZluxfeNF+zRz5jrw==",
+ "dev": true,
+ "dependencies": {
+ "codec-parser": "2.4.3"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
"node_modules/mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
@@ -43692,6 +43802,19 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/opus-decoder": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/opus-decoder/-/opus-decoder-0.7.6.tgz",
+ "integrity": "sha512-5QYSl1YQYbSzWL7vM4dJoyrLC804xIvBFjfKTZZ6/z/EgmdFouOTT+8PDM2V18vzgnhRNPDuyB2aTfl/2hvMRA==",
+ "dev": true,
+ "dependencies": {
+ "@wasm-audio-decoders/common": "9.0.5"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
"node_modules/ora": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz",
@@ -46375,6 +46498,16 @@
"integrity": "sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og==",
"dev": true
},
+ "node_modules/simple-yenc": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/simple-yenc/-/simple-yenc-1.0.4.tgz",
+ "integrity": "sha512-5gvxpSd79e9a3V4QDYUqnqxeD4HGlhCakVpb6gMnDD7lexJggSBJRBO5h52y/iJrdXRilX9UCuDaIJhSWm5OWw==",
+ "dev": true,
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -47574,6 +47707,26 @@
"integrity": "sha512-0K91MEXFpBUaywiwSSkmKjnGcasG/rVBXFLJz5DrgGabpYD6N+3yZrfD6uUIfpuTu65DZLHi7N8CizHc07BPZA==",
"dev": true
},
+ "node_modules/synaudio": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/synaudio/-/synaudio-0.3.5.tgz",
+ "integrity": "sha512-WBrjnWN3fCj2aESk/lzvD7PdfFqe6Sg0u4P0J4ZR9jxFq7cljEEql0YsyH53xtBT3bksCTG46AhhXVvCkzCXOw==",
+ "dev": true,
+ "dependencies": {
+ "@eshaz/web-worker": "1.2.1",
+ "simple-yenc": "^1.0.2"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/eshaz"
+ }
+ },
+ "node_modules/synaudio/node_modules/@eshaz/web-worker": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@eshaz/web-worker/-/web-worker-1.2.1.tgz",
+ "integrity": "sha512-v5AKAVtM0toVD2rDCGjzhySWlXG/sG5HVialdzrxFKTAnFZNCjQelX0n2tPK0tE86jf4s3hpWlpRtOh8OObktg==",
+ "dev": true
+ },
"node_modules/sync-disk-cache": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sync-disk-cache/-/sync-disk-cache-2.1.0.tgz",
diff --git a/package.json b/package.json
index e0dc72f..a7923d5 100644
--- a/package.json
+++ b/package.json
@@ -91,6 +91,7 @@
"eslint-plugin-n": "^17.3.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-qunit": "^8.1.1",
+ "icecast-metadata-player": "^1.17.3",
"instantsearch.css": "^8.1.0",
"instantsearch.js": "^4.67.0",
"interactjs": "^1.10.27",
diff --git a/tests/integration/components/icecast-player-test.js b/tests/integration/components/icecast-player-test.js
new file mode 100644
index 0000000..8d0bcc7
--- /dev/null
+++ b/tests/integration/components/icecast-player-test.js
@@ -0,0 +1,26 @@
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'crimeisdown/tests/helpers';
+import { render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+
+module('Integration | Component | icecast-player', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.set('myAction', function(val) { ... });
+
+ await render(hbs``);
+
+ assert.dom().hasText('');
+
+ // Template block usage:
+ await render(hbs`
+
+ template block text
+
+ `);
+
+ assert.dom().hasText('template block text');
+ });
+});
diff --git a/tests/unit/routes/airshow-test.js b/tests/unit/routes/airshow-test.js
new file mode 100644
index 0000000..50370e8
--- /dev/null
+++ b/tests/unit/routes/airshow-test.js
@@ -0,0 +1,11 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'crimeisdown/tests/helpers';
+
+module('Unit | Route | airshow', function (hooks) {
+ setupTest(hooks);
+
+ test('it exists', function (assert) {
+ let route = this.owner.lookup('route:airshow');
+ assert.ok(route);
+ });
+});
diff --git a/worker/src/index.js b/worker/src/index.js
index 2836f58..1c41120 100644
--- a/worker/src/index.js
+++ b/worker/src/index.js
@@ -1,86 +1,85 @@
export default {
- async fetch(request) {
- const corsHeaders = {
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
- "Access-Control-Max-Age": "86400",
- };
+ async fetch(request) {
+ const corsHeaders = {
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
+ 'Access-Control-Max-Age': '86400',
+ };
- // The URL for the remote third party API you want to fetch from
- // but does not implement CORS
- const API_URL = "https://www.youtube.com/@EricTendian/live";
+ // The URL for the remote third party API you want to fetch from
+ // but does not implement CORS
+ const API_URL = 'https://www.youtube.com/@EricTendian/live';
- // The endpoint you want the CORS reverse proxy to be on
- const PROXY_ENDPOINT = "/youtubelive/";
+ // The endpoint you want the CORS reverse proxy to be on
+ const PROXY_ENDPOINT = '/youtubelive/';
- async function handleRequest(originalRequest) {
- // Rewrite request to point to API URL. This also makes the request mutable
- // so you can add the correct Origin header to make the API server think
- // that this request is not cross-site.
- request = new Request(API_URL, originalRequest);
- request.headers.set("Origin", new URL(API_URL).origin);
- let response = await fetch(request);
- // Recreate the response so you can modify the headers
+ async function handleRequest(originalRequest) {
+ // Rewrite request to point to API URL. This also makes the request mutable
+ // so you can add the correct Origin header to make the API server think
+ // that this request is not cross-site.
+ request = new Request(API_URL, originalRequest);
+ request.headers.set('Origin', new URL(API_URL).origin);
+ let response = await fetch(request, {
+ cf: {
+ cacheTtl: 3600,
+ cacheEverything: true,
+ },
+ });
+ // Recreate the response so you can modify the headers
- response = new Response(response.body, response);
- // Set CORS headers
+ response = new Response(response.body, response);
+ // Set CORS headers
- response.headers.set("Access-Control-Allow-Origin", originalRequest.headers.get("Origin"));
+ response.headers.set('Access-Control-Allow-Origin', originalRequest.headers.get('Origin'));
- // Append to/Add Vary header so browser will cache response correctly
- response.headers.append("Vary", "Origin");
+ // Append to/Add Vary header so browser will cache response correctly
+ response.headers.append('Vary', 'Origin');
- return response;
- }
+ return response;
+ }
- async function handleOptions(request) {
- if (
- request.headers.get("Origin") !== null &&
- request.headers.get("Access-Control-Request-Method") !== null &&
- request.headers.get("Access-Control-Request-Headers") !== null
- ) {
- // Handle CORS preflight requests.
- return new Response(null, {
- headers: {
- ...corsHeaders,
- "Access-Control-Allow-Headers": request.headers.get(
- "Access-Control-Request-Headers"
- ),
- },
- });
- } else {
- // Handle standard OPTIONS request.
- return new Response(null, {
- headers: {
- Allow: "GET, HEAD, POST, OPTIONS",
- },
- });
- }
- }
+ async function handleOptions(request) {
+ if (
+ request.headers.get('Origin') !== null &&
+ request.headers.get('Access-Control-Request-Method') !== null &&
+ request.headers.get('Access-Control-Request-Headers') !== null
+ ) {
+ // Handle CORS preflight requests.
+ return new Response(null, {
+ headers: {
+ ...corsHeaders,
+ 'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers'),
+ },
+ });
+ } else {
+ // Handle standard OPTIONS request.
+ return new Response(null, {
+ headers: {
+ Allow: 'GET, HEAD, POST, OPTIONS',
+ },
+ });
+ }
+ }
- const url = new URL(request.url);
- if (url.pathname.startsWith(PROXY_ENDPOINT)) {
- if (request.method === "OPTIONS") {
- // Handle CORS preflight requests
- return handleOptions(request);
- } else if (
- request.method === "GET" ||
- request.method === "HEAD" ||
- request.method === "POST"
- ) {
- // Handle requests to the API server
- return handleRequest(request);
- } else {
- return new Response(null, {
- status: 405,
- statusText: "Method Not Allowed",
- });
- }
- } else {
- return new Response(null, {
- status: 404,
- statusText: "Page Not Found",
- });
- }
- },
+ const url = new URL(request.url);
+ if (url.pathname.startsWith(PROXY_ENDPOINT)) {
+ if (request.method === 'OPTIONS') {
+ // Handle CORS preflight requests
+ return handleOptions(request);
+ } else if (request.method === 'GET' || request.method === 'HEAD' || request.method === 'POST') {
+ // Handle requests to the API server
+ return handleRequest(request);
+ } else {
+ return new Response(null, {
+ status: 405,
+ statusText: 'Method Not Allowed',
+ });
+ }
+ } else {
+ return new Response(null, {
+ status: 404,
+ statusText: 'Page Not Found',
+ });
+ }
+ },
};
diff --git a/worker/test/index.spec.js b/worker/test/index.spec.js
index 3529146..1d8662c 100644
--- a/worker/test/index.spec.js
+++ b/worker/test/index.spec.js
@@ -1,4 +1,4 @@
-import { env, createExecutionContext, waitOnExecutionContext, SELF } from 'cloudflare:test';
+import { env, createExecutionContext, waitOnExecutionContext } from 'cloudflare:test';
import { describe, it, expect } from 'vitest';
import worker from '../src';