Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 38 additions & 28 deletions chrome/player/assets/fluidplayer/css/fluidplayer.css
Original file line number Diff line number Diff line change
Expand Up @@ -737,54 +737,64 @@ body {
background-color: var(--nextvideo-badge-background-color);
}

/* The next button and its autoplay switch: one tool in the bar. */
.fluid_tool_next {
display: flex;
/* Autoplay next (his wish, 2026-10-11): a tool of its own, always in the bar, like YouTube's
switch - this tab's setting; the options page's is where a new tab starts. The knob shows
play when on, pause when off; on, it sits right and the track takes the badge color. */
.fluid_button.fluid_autoplay_switch {
display: inline-flex;
align-items: center;
gap: 3px;
flex: none;
justify-content: center;
width: 36px;
cursor: pointer;
}

/* Autoplay next: a switch, on when its knob is right and the track takes the badge color. */
.fluid_autoplay_switch {
.fluid_autoplay_track {
position: relative;
flex: none;
width: 24px;
display: block;
width: 28px;
height: 12px;
border-radius: 6px;
background-color: var(--menu-text-color);
opacity: 0.35;
cursor: pointer;
transition: opacity 0.2s, background-color 0.2s;
}

.fluid_autoplay_switch:hover {
opacity: 0.6;
background-color: color-mix(in srgb, var(--menu-text-color) 35%, transparent);
transition: background-color 0.2s;
}

.fluid_autoplay_switch.on {
.fluid_autoplay_switch.on .fluid_autoplay_track {
background-color: var(--nextvideo-badge-background-color);
opacity: 1;
}

.fluid_autoplay_knob {
position: absolute;
top: 1px;
left: 1px;
width: 10px;
height: 10px;
top: -3px;
left: -2px;
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: var(--menu-text-color);
color: var(--menu-background-color);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
transition: transform 0.2s;
}

.fluid_autoplay_switch.on .fluid_autoplay_knob {
transform: translateX(12px);
transform: translateX(14px);
}

.fluid_autoplay_switch:focus-visible {
outline: 2px solid var(--menu-text-color);
outline-offset: 1px;
.fluid_button .fluid_autoplay_knob svg {
width: 12px;
height: 12px;
opacity: 1;
}

.fluid_autoplay_switch .fluid_autoplay_play,
.fluid_autoplay_switch.on .fluid_autoplay_pause {
display: none;
}

.fluid_autoplay_switch.on .fluid_autoplay_play {
display: inline-block;
}

#update_notif_banner,
Expand Down Expand Up @@ -2189,7 +2199,7 @@ div:not(.reordering)>.volume_block:focus .tooltip .tooltiptext {
/* Make shake */
.reordering>div>svg,
.reordering>div.volume_block>div>svg,
.reordering>.fluid_tool_next>div>svg {
.reordering>.fluid_autoplay_switch>.fluid_autoplay_track {
animation: tilt-shaking 0.25s infinite;
}

Expand Down
21 changes: 12 additions & 9 deletions chrome/player/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,19 @@
<use href="assets/fluidplayer/static/icons.svg#previous"></use>
</svg>
</div>
<div class="fluid_tool_next">
<div role="button" data-i18n-label="player_next_video_label" class="fluid_button fluid_button_next">
<svg>
<use href="assets/fluidplayer/static/icons.svg#next"></use>
</svg>
<div class="nextvideo_banner" data-i18n="player_nextvideo_auto" style="display: none;"></div>
</div>
<div role="switch" aria-checked="false" data-i18n-label="options_general_autoplaynext"
class="fluid_autoplay_switch"><span class="fluid_autoplay_knob"></span></div>
<div role="button" data-i18n-label="player_next_video_label" class="fluid_button fluid_button_next">
<svg>
<use href="assets/fluidplayer/static/icons.svg#next"></use>
</svg>
<div class="nextvideo_banner" data-i18n="player_nextvideo_auto" style="display: none;"></div>
</div>
<div role="switch" aria-checked="false" data-i18n-label="options_general_autoplaynext"
class="fluid_button fluid_autoplay_switch"><span class="fluid_autoplay_track"><span
class="fluid_autoplay_knob"><svg class="fluid_autoplay_play">
<use href="assets/fluidplayer/static/icons.svg#play"></use>
</svg><svg class="fluid_autoplay_pause">
<use href="assets/fluidplayer/static/icons.svg#pause"></use>
</svg></span></span></div>
<div role="button" data-i18n-label="player_sourcesbrowser_open_label"
class="fluid_button fluid_button_link">
<svg>
Expand Down
4 changes: 4 additions & 0 deletions chrome/player/options/defaults/ToolSettings.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export const DefaultToolSettings = {
'location': 'left',
'priority': 300,
},
'autoplay': {
'location': 'left',
'priority': 350,
},
'volume': {
'location': 'left',
'priority': 400,
Expand Down
1 change: 0 additions & 1 deletion chrome/player/ui/DOMElements.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const DOMElements = {
pip: document.querySelector('.mainplayer .fluid_button_pip'),
nextVideo: document.querySelector('.mainplayer .fluid_button_next'),
// The next button and the autoplay switch beside it: one tool in the bar.
nextVideoTool: document.querySelector('.mainplayer .fluid_tool_next'),
autoplaySwitch: document.querySelector('.mainplayer .fluid_autoplay_switch'),
previousVideo: document.querySelector('.mainplayer .fluid_button_previous'),
muteBtn: document.querySelector('.mainplayer .fluid_control_mute'),
Expand Down
4 changes: 2 additions & 2 deletions chrome/player/ui/InterfaceController.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -614,8 +614,8 @@ export class InterfaceController {
this.toggleAutoplayNext();
});

// The autoplay switch beside it (his wish, 2026-10-11): what the right click on the
// next button did, for this tab, which nobody found. The options page's setting is
// The autoplay switch, a tool of its own (his wish, 2026-10-11): what the right click on
// the next button did, for this tab, which nobody found. The options page's setting is
// where a new tab starts.
DOMElements.autoplaySwitch.tabIndex = 0;
DOMElements.autoplaySwitch.addEventListener('click', (e) => {
Expand Down
8 changes: 5 additions & 3 deletions chrome/player/ui/ToolManager.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,9 @@ export class ToolManager {
DOMElements.previousVideo.classList.add('hidden');
}

// The next button with its autoplay switch.
DOMElements.nextVideoTool.classList.toggle('hidden', !this.client.hasNextVideo());
// The next button only with a next video; the autoplay switch always (his wish, 2026-10-11:
// it went with the next button, and was never there where FastStream found no next link).
DOMElements.nextVideo.classList.toggle('hidden', !this.client.hasNextVideo());

// The audio-track list's button: only where there is a track to choose (2026-10-10; it
// opened the audio settings until they went).
Expand All @@ -158,7 +159,8 @@ export class ToolManager {
playpause: DOMElements.playPauseButton,
volume: DOMElements.volumeBlock,
duration: DOMElements.duration,
next: DOMElements.nextVideoTool,
next: DOMElements.nextVideo,
autoplay: DOMElements.autoplaySwitch,
previous: DOMElements.previousVideo,
pip: DOMElements.pip,
screenshot: DOMElements.screenshot,
Expand Down
28 changes: 19 additions & 9 deletions tests/e2e/specs/player-controls.e2e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -358,15 +358,20 @@ describe('Player controls', function() {
expect(await settle(banner, (display) => display === 'none')).toBe('none');
});

it('turns autoplay next on and off with the switch beside the next button', async function() {
it('turns autoplay next on and off with its switch, which is always in the bar', async function() {
// His wish (2026-10-11): only a right click on the next button did it, and nobody found it.
// Then the switch went with the next button, and so was never there where FastStream
// found no next link on the page: now it is a tool of its own, like YouTube's.
await openEmptyPlayer();
await addSource(mp4Url());
await waitForPicture();
const read = () => browser.execute(() => {
const tool = document.querySelector('.mainplayer .fluid_tool_next');
const shown = (el) => getComputedStyle(el).display !== 'none' && el.getBoundingClientRect().width > 0;
const toggle = document.querySelector('.mainplayer .fluid_autoplay_switch');
return {shown: getComputedStyle(tool).display !== 'none' && toggle.getBoundingClientRect().width > 0,
const play = shown(toggle.querySelector('.fluid_autoplay_play'));
const pause = shown(toggle.querySelector('.fluid_autoplay_pause'));
return {shown: shown(toggle), next: shown(document.querySelector('.mainplayer .fluid_button_next')),
icon: play && !pause ? 'play' : pause && !play ? 'pause' : 'both or none',
checked: toggle.getAttribute('aria-checked'), option: window.fastStream.options.autoplayNext,
session: sessionStorage.getItem('autoplayNext'),
badge: document.querySelector('.mainplayer .nextvideo_banner').style.display};
Expand All @@ -378,20 +383,25 @@ describe('Player controls', function() {
window.fastStream.hasNextVideo = () => false;
window.fastStream.interfaceController.updateToolVisibility();
});
expect((await read()).shown).toBe(false);
// No next link known: the next button is gone, the switch is not.
expect(await read()).toEqual({shown: true, next: false, icon: 'pause', checked: 'false', option: false,
session: null, badge: 'none'});

await (await browser.$('.mainplayer .fluid_autoplay_switch')).click();
expect(await read()).toEqual({shown: true, next: false, icon: 'play', checked: 'true', option: true,
session: 'true', badge: ''});

await browser.execute(() => {
window.fastStream.hasNextVideo = () => true;
window.fastStream.interfaceController.updateToolVisibility();
});
expect(await read()).toEqual({shown: true, checked: 'false', option: false, session: null, badge: 'none'});

await (await browser.$('.mainplayer .fluid_autoplay_switch')).click();
expect(await read()).toEqual({shown: true, checked: 'true', option: true, session: 'true', badge: ''});
expect(await read()).toEqual({shown: true, next: true, icon: 'play', checked: 'true', option: true,
session: 'true', badge: ''});

await browser.execute(() => document.querySelector('.mainplayer .fluid_autoplay_switch').focus());
await browser.keys([' ']);
expect(await read()).toEqual({shown: true, checked: 'false', option: false, session: 'false', badge: 'none'});
expect(await read()).toEqual({shown: true, next: true, icon: 'pause', checked: 'false', option: false,
session: 'false', badge: 'none'});
});

it('shows the picture in the aspect ratio chosen, stretched, or as it is', async function() {
Expand Down
Loading