Open API, เว็บฮุกและ Embed player

สำหรับนักพัฒนาที่ต้องการนำข้อมูลสถานีไปแสดงในเว็บไซต์ แอป หรือระบบอื่น

ภาพรวม

  • เอกสารแบบเครื่องอ่านได้: /api/v1/openapi.json (OpenAPI 3.1) ไม่ต้องใช้คีย์
  • ข้อมูลสาธารณะไม่ต้องใช้คีย์ ข้อมูลส่วนตัวและการควบคุมต้องใช้คีย์ API ของสถานี
  • เวลาเป็น ISO 8601 แบบ UTC
  • /api/v1/ ตอบทุกโดเมน (เปิด CORS) และรับ GET, POST และ OPTIONS
  • การจำกัดจำนวนคำขอนับตามที่อยู่ IP ถ้าเกิน ระบบตอบ 429
  • {slug} คือชื่อย่อสถานี แบบเดียวกับใน /s/{slug} สถานีที่ไม่มีหรือถูกระงับตอบ 404 พร้อม {"error":"not_found"}

ข้อมูลสาธารณะ (ไม่ต้องใช้คีย์)

คำขอ ได้อะไร จำกัด
GET /api/v1/stations สารบบสถานี: items แต่ละรายการมีข้อมูลสถานี on_air และ now_playing 300 ครั้งต่อนาที
GET /api/v1/stations/{slug} ข้อมูลสถานีและลิงก์สตรีม 300 ครั้งต่อนาที
GET /api/v1/stations/{slug}/nowplaying สิ่งที่กำลังออกอากาศ 300 ครั้งต่อนาที
GET /api/v1/stations/{slug}/schedule ผังรายการล่วงหน้า 300 ครั้งต่อนาที
GET /api/v1/stations/{slug}/history เพลงที่เพิ่งเล่น 300 ครั้งต่อนาที
GET /api/v1/stations/{slug}/episodes ตอนฟังย้อนหลังที่เผยแพร่แล้ว 300 ครั้งต่อนาที

รายละเอียด

  • stations: กรองด้วย q, genre, province, language (th หรือ en) การค้นหาเข้าใจภาษาไทยที่ไม่ใส่วรรณยุกต์ ข้อความที่พิมพ์ผิดแป้นภาษา และภาษาไทยที่เขียนเป็นอักษรโรมัน แสดงเฉพาะสถานีที่อยู่ในสารบบ
  • ข้อมูลสถานี: slug, name, tagline, language, timezone, genre, province, url, logo (url, alt) หรือ null, embed_url และ streams สตรีม: มี mp3 เสมอ มี aac 48 kbps เมื่อสถานีเปิดสตรีมประหยัดเน็ต มี hls เมื่อเปิด HLS
  • nowplaying: on_air, source (live, autodj, scheduled, fallback หรือ offline), now_playing (title, artist, source, started_at, show), show (name, host, starts_at, ends_at) หรือ null, listeners, capacity (null = ไม่จำกัด) และ alerts (ประกาศฉุกเฉินที่ใช้งานอยู่: id, severity, title, message, instruction, language, expires_at)
  • schedule: days ตั้งแต่ 1 ถึง 31 ค่าเริ่มต้น 7 แต่ละรายการมี show, host, kind (live, prerecorded, playlist), starts_at, ends_at
  • history: limit ตั้งแต่ 1 ถึง 50 ค่าเริ่มต้น 10 แต่ละรายการมี title, artist, source, started_at
  • episodes: 50 ตอนล่าสุดที่เผยแพร่แล้ว: id, title, show, published_at, duration_s, url, audio_url, transcript_url โดย transcript_url เป็นไฟล์ WebVTT หรือเป็น null จนกว่าทีมงานสถานีจะอนุมัติบทถอดเสียง

ข้อมูลส่วนตัวและการควบคุม (ต้องใช้คีย์)

ผู้ดูแลสถานีสร้างคีย์ได้ที่ จัดการสถานี → เชื่อมต่อบริการอื่น → คีย์ Open API เลือกสิทธิ์ read (เลือกไว้ให้แล้ว) และ control คีย์ขึ้นต้นด้วย rp_ และแสดงครั้งเดียว ส่งคีย์แบบนี้

Authorization: Bearer rp_...
คำขอ สิทธิ์ จำกัด ได้อะไร
GET /api/v1/stations/{slug}/stats?from=YYYY-MM-DD&to=YYYY-MM-DD read 300 ครั้งต่อนาที ตัวเลขเดียวกับหน้าสถิติ: summary, byDay, byHour, byStream, byClient, byCountry, byLength, topSongs, topShows
GET /api/v1/stations/{slug}/listeners read 300 ครั้งต่อนาที total และ by_stream (ผู้ฟังแยกตามสตรีม)
POST /api/v1/stations/{slug}/skip control 20 ครั้งต่อนาที {"ok": true} เมื่อข้ามเพลงของเล่นเพลงอัตโนมัติแล้ว
  • stats: ถ้าไม่ระบุวันที่ ได้ 7 วันล่าสุดถึงวันนี้ตามเขตเวลาของสถานี ถ้า from อยู่หลัง to หรือช่วงยาวเกิน 370 วัน ระบบตอบ 400 พร้อม {"error":"bad_range"}
  • skip: ตอบ 409 ขณะดีเจออกอากาศสด (ให้ข้ามเพลงจากห้องจัดรายการแทน) ตอบ 502 เมื่อระบบออกอากาศไม่ตอบ
  • ไม่มีคีย์ คีย์ผิด หรือคีย์ไม่มีสิทธิ์ที่ต้องใช้ ระบบตอบ 401 พร้อมส่วนหัว WWW-Authenticate ที่บอกชื่อสิทธิ์

สถานะ

คำขอ ใช้ทำอะไร
GET /api/public/stations/{slug}/status สถานะเครื่องเล่นของสถานีหนึ่ง ไม่ต้องใช้คีย์ ไม่จำกัดจำนวนคำขอ เก็บแคช 2 วินาที
GET /status.json สถานะแพลตฟอร์มสำหรับระบบเฝ้าระวัง ไม่ต้องใช้คีย์ เปิด CORS

สถานะสถานี ส่ง station (slug, name), online, source, now_playing, show (name, ends_at), next_show (name, starts_at) ภายใน 7 วัน, listeners, capacity และ full ถ้าสถานีจำกัดการฟังตามประเทศ จะส่ง region_blocked สำหรับผู้เรียกด้วย

/status.json ส่ง time, components (website, database, streaming: up หรือ down), target_pct และข้อมูลของแต่ละสถานีที่เปิดอยู่: slug, name, engine, source, off_air, availability_30d_pct, measured_minutes_30d พร้อม incidents ใน 14 วันที่ผ่านมา (station, start, end, minutes, cause, ongoing) ถ้าผู้ดูแลระบบซ่อนความพร้อมใช้งานในหน้าสถานะ ตัวเลขความพร้อมใช้งานเป็น null และ incidents ว่าง

เหตุการณ์สด (Server-Sent Events)

GET /api/public/stations/{slug}/events เปิดการเชื่อมต่อ text/event-stream ค้างไว้ ไม่ต้องใช้คีย์ เมื่อเชื่อมต่อ จะได้เพลงปัจจุบันและประกาศที่ใช้งานอยู่ทันที ระบบส่งบรรทัดหมายเหตุ : ping ทุก 25 วินาที

เหตุการณ์ ข้อมูล
nowplaying เพลงใหม่: title, artist, source, started_at และ show, media_id ถ้ามี
chat แชตของสถานี: type เป็น message (id, display_name, body, is_staff, created_at) หรือ delete (id)
alert { "type": "alert", "alert": { id, severity, title, message, instruction, language, expires_at } }
alert_end { "type": "alert_end", "id": ... } เมื่อประกาศสิ้นสุด
const es = new EventSource('https://cfbtradio.com/api/public/stations/saint-fm/events');
es.addEventListener('nowplaying', (e) => console.log(JSON.parse(e.data)));
es.addEventListener('alert', (e) => console.log(JSON.parse(e.data).alert));

เว็บฮุก

ผู้ดูแลสถานีเพิ่มเว็บฮุกได้ที่ จัดการสถานี → เชื่อมต่อบริการอื่น → เว็บฮุก ใส่ลิงก์ปลายทาง (HTTPS) และเลือกเหตุการณ์ Secret สำหรับตรวจลายมือชื่อขึ้นต้นด้วย whsec_ และแสดงครั้งเดียว ปุ่ม "ส่งทดสอบ" ส่งเหตุการณ์ test

เหตุการณ์ เมื่อไร
track.started เริ่มเพลงหรือรายการใหม่
live.started ดีเจเริ่มออกอากาศสด
live.ended ดีเจจบการออกอากาศสด
request.received มีคำขอเพลงหรือข้อความใหม่
episode.published เผยแพร่ตอนฟังย้อนหลัง
listeners.peak ผู้ฟังพร้อมกันสูงสุดใหม่ของวัน
station.silence สัญญาณเงียบหรือกลับมามีเสียง

การส่งแต่ละครั้งเป็น POST แบบ JSON เนื้อหาคือ { id, event, station_id, created_at, data } พร้อมส่วนหัวต่อไปนี้

ส่วนหัว ค่า
X-Radio-Event ชื่อเหตุการณ์
X-Radio-Delivery หมายเลขการส่ง
X-Radio-Timestamp เวลา Unix เป็นวินาที
X-Radio-Signature sha256= ตามด้วย HMAC-SHA256 แบบเลขฐานสิบหกของ timestamp + "." + body โดยใช้ Secret เป็นกุญแจ

วิธีตรวจ: คำนวณลายมือชื่อจากเนื้อหาดิบ เทียบแบบใช้เวลาคงที่ ปฏิเสธเวลาที่เก่าเกินไป (เช่น เกิน 5 นาที) และใช้ id กันการรับซ้ำ

const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(`${req.headers['x-radio-timestamp']}.${rawBody}`).digest('hex');
const given = Buffer.from(req.headers['x-radio-signature'] || '');
if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(expected), given)) return res.status(401).end();

กติกาการส่ง

  • ตอบด้วยรหัส 2xx ใดก็ได้ภายใน 10 วินาที ระบบไม่ตามการเปลี่ยนเส้นทาง (redirect)
  • ถ้าส่งไม่สำเร็จ ระบบลองใหม่ 5 ครั้ง หลัง 30 วินาที 1, 2, 4 และ 8 นาที
  • ระบบไม่ส่งไปยังที่อยู่ในเครือข่ายภายในของแพลตฟอร์ม เว้นแต่เซิร์ฟเวอร์อนุญาต
  • หน้าเชื่อมต่อบริการอื่นแสดงการส่ง 5 ครั้งล่าสุดของแต่ละเว็บฮุกบันทึกการส่งเก็บไว้ 30 วัน

Embed player

คัดลอกโค้ดจาก จัดการสถานี → เชื่อมต่อบริการอื่น → เครื่องเล่นสำหรับฝังในเว็บอื่น (Embed)

<iframe src="https://cfbtradio.com/s/<station>/embed" title="เครื่องเล่นวิทยุ: ชื่อสถานี" width="100%" height="190" style="border:0;max-width:34rem" allow="autoplay"></iframe>
ตัวเลือก ค่า ค่าเริ่มต้น
theme light, dark ตามการตั้งค่าของอุปกรณ์
lang th, en ภาษาหลักของสถานี

ตัวอย่าง: https://cfbtradio.com/s/<station>/embed?theme=dark&lang=en

เครื่องเล่นนี้

  • ไม่เล่นอัตโนมัติ
  • มีปุ่มเล่น/หยุดที่บอกสถานะ แถบปรับระดับเสียงที่มีชื่อกำกับ (เริ่มที่ 80) และลิงก์ไปเว็บสถานี
  • มีช่อง "อ่านชื่อเพลงเมื่อเปลี่ยน" และช่อง "ประหยัดเน็ต" เมื่อสถานีมีสตรีมประหยัดเน็ต
  • บอกเมื่อสถานีเต็ม ออฟไลน์ หรือไม่เปิดให้ฟังในประเทศของผู้ฟัง และเชื่อมต่อใหม่เมื่อสัญญาณหลุด
  • แสดงประกาศฉุกเฉินด้านบนและอ่านออกเสียง

ต้องคงแอตทริบิวต์ title ไว้เสมอ เพื่อให้โปรแกรมอ่านหน้าจอบอกได้ว่ากรอบนี้คืออะไร เครื่องเล่นของสถานีที่ถูกระงับจะแสดงว่าไม่พบ