DeadAir made a shortened copy of each episode with ffmpeg: as much disk again as the original, one pause length for everyone, and a minutes-long wait after the download. Seeking past silences instead was tried on an iPhone and left about 1.3 s of nothing at every cut (#178). Now it finds a file's silences once (silencedetect, plus ffprobe for each frame's byte position) and stores a plan for each pause a person can choose, 0.2 to 0.5 s: the cuts, and the frames kept as byte ranges of the original. /media/{id}/deadair.m3u8 hands this person's plan out as an HLS playlist of ranges of /media/{id}/deadair.mp3, the original. Tested on iOS Safari first (#180): no gap at the cuts, plays locked, seeks exactly, and needs no ID3 timestamp in front of each piece. Other browsers get hls.js (1.6.19, the light build), loaded only when needed. Not where canPlayType says it can play HLS: Chromium answers "maybe" and then fails on MP3 ranges with "stream parsing failed", so only Apple's browsers play the playlist themselves. The pause is per person and per show, on deadair_prefs.pause_ms, 0.3 s unless chosen; 0.2 s, what the copies left, made speech sound rushed. AAC in MP4 has no ranges a playlist can use, so only MP3 and ADTS AAC are shortened (the library is 803 MP3s and no .m4a). The copies, their table and #179's expiry go: the first start drops the table and deletes the files it names, only those named name.deadair.*. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1751,7 +1751,7 @@ test('a feed\'s settings say what downloads, take words as chips, and keep what
|
||||
await page.keyboard.press('Escape');
|
||||
});
|
||||
|
||||
test('DeadAir is off until turned on for a show, then plays its shortened copy and switches back', async ({ page }) => {
|
||||
test('DeadAir is off until turned on for a show, then plays it shortened, with your pause, and switches back', async ({ page }) => {
|
||||
// #171/#172. A show of its own, with real audio, added here and left at the end.
|
||||
const { id } = await (await page.request.post('/api/feeds', { data: { url: 'http://127.0.0.1:8792/quiet.xml' } })).json();
|
||||
const entry = async () => {
|
||||
@@ -1761,7 +1761,7 @@ test('DeadAir is off until turned on for a show, then plays its shortened copy a
|
||||
await expect.poll(async () => (await entry())?.guid, { timeout: 15_000 }).toBe('quiet-1');
|
||||
await page.request.post(`/api/feeds/${encodeURIComponent(id)}/download-latest`, { data: { count: 1 } });
|
||||
await expect.poll(async () => !!(await entry())?.enclosures[0]?.path, { timeout: 15_000 }).toBe(true);
|
||||
// Off for a show until someone turns it on: no copy is made.
|
||||
// Off for a show until someone turns it on: nothing is cut.
|
||||
expect((await entry()).enclosures[0].deadair).toBeUndefined();
|
||||
await page.reload();
|
||||
|
||||
@@ -1772,16 +1772,26 @@ test('DeadAir is off until turned on for a show, then plays its shortened copy a
|
||||
await expect(sw).not.toBeChecked();
|
||||
await sw.locator('xpath=..').click();
|
||||
await page.locator('#ssave').click();
|
||||
// Made at once, not at the next pass, and the row says so when it is done without a reload:
|
||||
// "Cutting dead air…" until then, then about 1.8 s off each of four 2 s silences.
|
||||
// Found at once, not at the next pass, and the row says so when it is done without a reload:
|
||||
// "Cutting dead air…" until then, then about 1.7 s off each of four 2 s silences.
|
||||
const row = page.locator('.ep', { hasText: 'Pauses Between' });
|
||||
await expect(row).toContainText('s shorter', { timeout: 20_000 });
|
||||
expect((await entry()).enclosures[0].deadair).toBeGreaterThan(5);
|
||||
await expect(row).not.toContainText('Cutting dead air');
|
||||
|
||||
// Played from the copy, with the switch beside the speed pressed.
|
||||
// A longer pause of your own (#182) saves less.
|
||||
const usual = (await entry()).enclosures[0].deadair;
|
||||
await page.locator('#content .acts [data-a="settings"]').click();
|
||||
await expect(page.locator('[name=sdpause][value="300"]')).toBeChecked();
|
||||
await page.locator('[name=sdpause][value="500"]').locator('xpath=..').click();
|
||||
await page.locator('#ssave').click();
|
||||
await expect.poll(async () => (await entry()).enclosures[0].deadair).toBeLessThan(usual);
|
||||
|
||||
// Played through DeadAir's playlist (#181), by hls.js in Chromium, with the switch beside the
|
||||
// speed pressed. The element's own src is then hls.js's blob: address.
|
||||
await row.locator('[data-a=play]').click();
|
||||
await expect.poll(() => page.evaluate(() => document.querySelector('#audio').getAttribute('src'))).toMatch(/\/deadair$/);
|
||||
await expect.poll(() => page.evaluate(() => player.src)).toMatch(/\/deadair\.m3u8$/);
|
||||
await expect.poll(() => page.evaluate(() => document.querySelector('#audio').currentTime), { timeout: 15_000 }).toBeGreaterThan(0.5);
|
||||
await expect(page.locator('#pdead')).toHaveAttribute('aria-pressed', 'true');
|
||||
// Off from the player: the original, from the same place.
|
||||
await page.locator('#pdead').click();
|
||||
|
||||
Reference in New Issue
Block a user