summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjade (winter)2025-12-05 00:34:03 +0000
committerjade (winter)2025-12-05 00:34:03 +0000
commit92d27365cadbd98e6d709673cd0729de8793c494 (patch)
treee7ac18fab25957d39745a865ef269f6b6733f15a
initial commit
-rw-r--r--index.php36
-rw-r--r--lib/bbcnext.php109
-rw-r--r--lib/rms.php10
-rw-r--r--search.php3
-rw-r--r--theme/Brutalist/main.css198
-rw-r--r--theme/Default/main.css250
6 files changed, 606 insertions, 0 deletions
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..b4a98e7
--- /dev/null
+++ b/index.php
@@ -0,0 +1,36 @@
+<?php
+require_once __DIR__ . '/lib/bbcnext.php';
+
+stream_context_set_default(['http' => [
+ 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36',
+]]);
+?>
+<!doctype html>
+<html lang="en-GB">
+ <head>
+ <meta charset="UTF-8"/>
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
+ <title></title>
+ <?php foreach (array_diff(scandir(__DIR__ . '/theme'), ['.', '..']) as $theme): ?>
+ <link rel="<?=$theme=='Default'?'':'alternate '?>stylesheet"
+ href="theme/<?=$theme?>/main.css"
+ title="<?=$theme?>"/>
+ <?php endforeach; ?>
+ </head>
+
+ <body>
+ <header>
+ <a href="."><frontend-name>RMS Frontend</frontend-name></a>
+ <form id="search-form" action="search.php">
+ <input name="q" placeholder="Query…"/>
+ <input type="submit" value="Search"/>
+ </form>
+ </header>
+
+ <main>
+ <?php
+ next_renderFromRms($_GET['path'] ?? RMS_SOUNDS_HOME);
+ ?>
+ </main>
+ </body>
+</html>
diff --git a/lib/bbcnext.php b/lib/bbcnext.php
new file mode 100644
index 0000000..988d248
--- /dev/null
+++ b/lib/bbcnext.php
@@ -0,0 +1,109 @@
+<?php
+
+require_once __DIR__ . '/rms.php';
+
+function next_renderFromRms(string $path) {
+ next_renderExperience(rms_fetch($path)->data);
+}
+
+function next_renderExperience(array $modules) {
+ foreach ($modules as $m) {
+ next_renderModule($m);
+ }
+}
+
+function next_renderModule(stdClass $module) {
+ $type = strtr($module->type, '_', '-');
+ $titleTag = $type == 'inline-header-module' ? 'h1' : 'h2';
+ echo "<$type id=\"$module->id\" module-style=\"$module->style\">";
+ echo "<$titleTag>"
+ . htmlspecialchars($module->title ?? '[no title]')
+ . "</$titleTag>";
+ if (isset($module->description)) {
+ echo '<module-description>'
+ . htmlspecialchars($module->description)
+ . '</module-description>';
+ }
+ echo '<module-items>';
+ if (!is_array($module->data)) {
+ next_renderItem($module->data);
+ } else {
+ foreach ($module->data as $item) {
+ next_renderItem($item);
+ }
+ }
+ echo "</module-items></$type>";
+}
+
+function next_renderItem(stdClass $item) {
+ $type = strtr($item->type, '_', '-');
+ $primaryTag = $type == 'segment-item' ? 'primary-title' : 'h3';
+ echo "<$type id=\"$item->id\">";
+ echo '<item-titles><item-first-titles>';
+ echo "<$primaryTag>"
+ . htmlspecialchars($item->titles?->primary ?? '[no primary]')
+ . "</$primaryTag>";
+ if (isset($item->network)) {
+ echo '<item-network>'
+ . htmlspecialchars($item->network->short_title)
+ . '</item-network>';
+ }
+ echo '</item-first-titles>';
+ if (isset($item->titles->secondary)) {
+ echo '<secondary-title>'
+ . htmlspecialchars($item->titles->secondary)
+ . '</secondary-title>';
+ }
+ if (isset($item->titles->tertiary)) {
+ echo '<tertiary-title>'
+ . htmlspecialchars($item->titles->tertiary)
+ . '</tertiary-title>';
+ }
+ echo '</item-titles>';
+ if (isset($item->synopses?->short, $item->synopses?->medium)) {
+ echo '<synopsis><details><summary>'
+ . htmlspecialchars($item->synopses->short)
+ . '</summary>'
+ . htmlspecialchars($item->synopses->long ?? $item->synopses->medium)
+ . '</details></synopsis>';
+ } elseif (isset($item->synopses?->short)) {
+ echo '<synopsis>' . htmlspecialchars($item->synopses->short)
+ . '</synopsis>';
+ }
+
+ if (isset($item->item)) {
+ next_renderItem($item->item);
+ }
+
+ if (isset($item->offset)) {
+ echo '<offset-start>'
+ . gmdate('H:i:s', $item->offset->start)
+ . '</offset-start>';
+ echo '<offset-end>'
+ . gmdate('H:i:s', $item->offset->end)
+ . '</offset-end>';
+ }
+
+ if ($type == 'container-item') {
+ $href = '?path=' . RMS_SOUNDS_CONTAINER . '/' . $item->urn;
+ echo "<a href=\"$href\">View</a>";
+ } elseif ($type == 'playable-item') {
+ $pid = explode(':', $item->urn)[4];
+ $href = '?path=' . RMS_SOUNDS_PLAYABLE . '/' . $pid;
+ echo "<a href=\"$href\">Listen</a>";
+ }
+
+ foreach ($item->uris ?? [] as $uri) {
+ if ($uri->type != 'link') continue;
+ echo '<a href="' . htmlspecialchars($uri->uri) . '" target="_blank">'
+ . htmlspecialchars($uri->label) . '</a>';
+ }
+
+ if (isset($item->download) && $item->download->type == 'non_drm') {
+ $variant = $item->download->quality_variants->high;
+ echo '<a href="' . htmlspecialchars($variant->file_url) . '">'
+ . "Download ($variant->label)</a>";
+ }
+
+ echo "</$type>";
+}
diff --git a/lib/rms.php b/lib/rms.php
new file mode 100644
index 0000000..1e76d53
--- /dev/null
+++ b/lib/rms.php
@@ -0,0 +1,10 @@
+<?php
+
+const RMS_API_BASE = 'https://rms.api.bbc.co.uk/v2';
+const RMS_SOUNDS_HOME = '/experience/inline/listen/sign-in';
+const RMS_SOUNDS_CONTAINER = '/experience/inline/container';
+const RMS_SOUNDS_PLAYABLE = '/experience/inline/play';
+
+function rms_fetch(string $path): stdClass {
+ return json_decode(file_get_contents(RMS_API_BASE . $path));
+}
diff --git a/search.php b/search.php
new file mode 100644
index 0000000..ad52c35
--- /dev/null
+++ b/search.php
@@ -0,0 +1,3 @@
+<?php
+
+header('Location: .?path=/experience/inline/search?q=' . urlencode($_GET['q']));
diff --git a/theme/Brutalist/main.css b/theme/Brutalist/main.css
new file mode 100644
index 0000000..52f82d2
--- /dev/null
+++ b/theme/Brutalist/main.css
@@ -0,0 +1,198 @@
+:root {
+ font-family: sans-serif;
+ background: #eee;
+}
+
+body {
+ margin: 0;
+}
+
+main {
+ width: 100vw;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+}
+
+header {
+ box-sizing: border-box;
+ width: calc(100vw - 64px);
+ background: black;
+ margin: 32px;
+ padding: 16px;
+ color: white;
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: 1fr auto;
+ align-items: center;
+
+ a {
+ color: white;
+ }
+
+ frontend-name {
+ font-size: 0;
+ &::after {
+ font-size: large;
+ font-weight: bold;
+ content: "Brutalist Sounds";
+ }
+ }
+
+ #search-form {
+ input {
+ background: black;
+ border: 1px solid white;
+ color: white;
+ font-family: inherit;
+ font-size: small;
+ padding: 4px 8px;
+ &::placeholder {
+ color: #bbb;
+ }
+ }
+ }
+}
+
+inline-display-module {
+ border: 1px solid black;
+ margin: 16px;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ height: fit-content;
+ max-height: 600px;
+ overflow-y: scroll;
+ box-sizing: border-box;
+ width: min(1000px, calc(100vw - 64px));
+ background: white;
+ color: black;
+
+ h2 {
+ top: -16px;
+ margin: -16px;
+ margin-bottom: 0;
+ position: sticky;
+ background: black;
+ color: white;
+ padding: 8px;
+ padding-left: 16px;
+ }
+
+ module-description {
+ font-weight: bold;
+ }
+}
+
+inline-header-module {
+ display: block;
+ padding: 16px;
+
+ h2 {
+ font-size: xx-large;
+ }
+
+ container-item {
+ display: none;
+ }
+}
+
+playable-item, container-item, display-item, broadcast-summary {
+ display: block;
+ padding: 8px;
+
+ item-first-titles {
+ margin: -8px;
+ margin-bottom: 8px;
+ background: black;
+ color: white;
+ padding: 0 8px;
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: 1fr auto;
+ align-items: baseline;
+ }
+
+ h3 {
+ margin: 0;
+ }
+
+ secondary-title {
+ display: block;
+ font-weight: bold;
+ }
+
+ tertiary-title {
+ display: block;
+ font-style: italic;
+ }
+
+ synopsis {
+ display: block;
+ margin-top: 8px;
+ white-space: pre-wrap;
+
+ details {
+ summary {
+ list-style: none;
+ &::after {
+ font-weight: bold;
+ content: ' Show more…';
+ }
+ }
+ &[open] {
+ summary {
+ display: none;
+ }
+ }
+ }
+ }
+
+ a {
+ font-style: italic;
+ display: block;
+ text-align: end;
+ &::after {
+ content: " »";
+ }
+ }
+}
+
+#aod_play_area, #single_item_promo {
+padding: 0;
+gap: 0;
+h2 {
+ margin: 0px;
+}
+playable-item, display-item {
+ h3, secondary-title, tertiary-title, synopsis {
+ padding: 0 8px;
+ }
+
+ a {
+ display: none;
+ }
+}
+}
+
+segment-item {
+ display: grid;
+ gap: 8px;
+ grid-auto-flow: column;
+ grid-auto-columns: 1fr auto auto;
+ border-bottom: 0.5px dashed black;
+
+ primary-title {
+ font-weight: bold;
+ margin-inline-end: 12px;
+ }
+ tertiary-title {
+ display: none;
+ }
+
+ offset-start::after {
+ margin-inline-start: 8px;
+ content: '—';
+ }
+}
diff --git a/theme/Default/main.css b/theme/Default/main.css
new file mode 100644
index 0000000..b2892fb
--- /dev/null
+++ b/theme/Default/main.css
@@ -0,0 +1,250 @@
+body {
+ background: #eee;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ font-size: 10pt;
+ margin: 0;
+ padding: 16px;
+ gap: 16px;
+ font-family: sans-serif;
+}
+
+a {
+ text-decoration: none;
+ color: #8a6040;
+}
+
+header, main {
+ width: min(800px, calc(100vw - 32px));
+ box-sizing: border-box;
+ background: white;
+ border-radius: 4px;
+ overflow: clip;
+ border: 2px solid grey;
+}
+
+header {
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: 1fr auto;
+ align-items: center;
+ background: #963;
+
+ > * {
+ padding: 8px;
+ }
+
+ a {
+ background: linear-gradient(to right, #ca9a7a, #963);
+ display: block;
+ color: white;
+ font-weight: bold;
+ width: max-content;
+ font-size: 0;
+ &::after {
+ font-size: 16pt;
+ content: 'BBC Sounds';
+ }
+ }
+}
+
+inline-display-module {
+ display: flex;
+ flex-direction: column;
+
+ h2 {
+ text-align: center;
+ margin: 0;
+ background: linear-gradient(to bottom, #ffeeca, #efba99);
+ padding: 8px;
+ border-top: 1px solid #daaa55;
+ margin-top: 16px;
+ }
+
+ item-titles {
+ display: grid;
+ background: linear-gradient(to bottom, #fefefe, #efefef);
+ padding: 8px;
+ gap: 4px;
+ border-top: 2px solid #eee;
+ border-bottom: 1px solid #eee;
+ }
+ item-first-titles {
+ grid-row: 1;
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: 1fr auto;
+ align-items: baseline;
+ }
+ h3 {
+ margin: 0;
+ }
+
+ item-network {
+ grid-row: 1;
+ grid-column: 2;
+ }
+ secondary-title {
+ font-weight: bold;
+ grid-row: 2;
+ grid-column: 1;
+ }
+ tertiary-title {
+ grid-row: 3;
+ grid-column: 1;
+ }
+
+ module-description, synopsis {
+ white-space: pre-wrap;
+ margin: 8px;
+ display: block;
+ }
+
+ details {
+ summary {
+ list-style: none;
+ &::after {
+ font-weight: bold;
+ content: ' More…';
+ }
+ }
+ &[open] summary {
+ font-size: 0;
+ &::after {
+ font-size: 10pt;
+ content: 'Less…';
+ }
+ }
+ }
+
+ a {
+ display: block;
+ background: #efefef;
+ padding: 4px;
+ text-align: center;
+ font-weight: bold;
+ }
+}
+
+inline-header-module {
+ display: block;
+ padding: 16px;
+ text-align: center;
+ module-items {
+ display: none;
+ }
+}
+
+#listen_sign_in_upsell {
+h2 { margin-top: 0; }
+module-description::after {
+ display: block;
+ margin-top: 8px;
+ content: "Please note this is an unofficial frontend to BBC services and is not affiliated with or endorsed by the BBC in any way.";
+}
+}
+inline-display-module:first-child h2 { margin-top: 0; }
+
+#listen_live {
+overflow-x: scroll;
+
+module-description {
+ display: none;
+}
+
+h2 {
+ position: sticky;
+ left: 0;
+}
+
+playable-item {
+ display: block;
+ border-right: 1px solid #ccc;
+ width: 300px;
+ background: #eee;
+}
+
+module-items {
+ display: flex;
+ flex-direction: row;
+ overflow-x: scroll;
+ border-bottom: 1px solid #ccc;
+ width: max-content;
+}
+}
+
+#single_item_promo {
+margin: 32px;
+border: 1px solid #963;
+border-radius: 4px;
+overflow: clip;
+> module-items > display-item > item-titles {
+ background: #ffeecb;
+ border: none;
+}
+h2 { display: none; }
+}
+
+#categories {
+display: flex;
+
+container-item {
+ display: flex;
+ align-items: baseline;
+ background: #efefef;
+ border-top: 1px solid #fec;
+ border-bottom: 1px solid #aaa;
+
+ item-titles {
+ background: none;
+ border: none;
+ }
+
+ a {
+ font-size: 0;
+ &::after {
+ font-size: 10pt;
+ content: "view category »"
+ }
+ }
+}
+}
+
+segment-item {
+ display: grid;
+ grid-template-columns: 1fr auto auto;
+ align-items: baseline;
+ padding: 0 8px 0 0;
+ background: linear-gradient(to bottom, #fff, #f8f8f8);
+ border-bottom: 1px solid #ccc;
+
+ item-titles {
+ background: none;
+ border: none;
+ }
+
+ primary-title {
+ grid-row: 1;
+ grid-column: 1;
+ }
+ secondary-title {
+ grid-row: 2;
+ grid-column: 1;
+ }
+ tertiary-title {
+ white-space: pre-wrap;
+ font-size: 8pt;
+ }
+ offset-start {
+ grid-row: 1;
+ grid-column: 2;
+ &::after {
+ content: '—';
+ }
+ }
+ offset-end {
+ grid-row: 1;
+ grid-column: 3;
+ }
+}