Skip to content

Commit

Permalink
Feature - Prevents multiple audio/video attachments from being played…
Browse files Browse the repository at this point in the history
… at the same time (mastodon#24717)
  • Loading branch information
TheDevJoao authored Nov 7, 2023
1 parent 389a6cc commit d3cd37d
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 11 deletions.
80 changes: 80 additions & 0 deletions app/javascript/mastodon/features/__tests__/toggle-play.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';

import { render, fireEvent } from '@testing-library/react';

class Media extends Component {

constructor(props) {
super(props);

this.state = {
paused: props.paused || false,
};
}

handleMediaClick = () => {
const { onClick } = this.props;

this.setState(prevState => ({
paused: !prevState.paused,
}));

if (typeof onClick === 'function') {
onClick();
}

const { title } = this.props;
const mediaElements = document.querySelectorAll(`div[title="${title}"]`);

setTimeout(() => {
mediaElements.forEach(element => {
if (element !== this && !element.classList.contains('paused')) {
element.click();
}
});
}, 0);
};

render() {
const { title } = this.props;
const { paused } = this.state;

return (
<button title={title} onClick={this.handleMediaClick}>
Media Component - {paused ? 'Paused' : 'Playing'}
</button>
);
}

}

Media.propTypes = {
title: PropTypes.string.isRequired,
onClick: PropTypes.func,
paused: PropTypes.bool,
};

describe('Media attachments test', () => {
let currentMedia = null;
const togglePlayMock = jest.fn();

it('plays a new media file and pauses others that were playing', () => {
const container = render(
<div>
<Media title='firstMedia' paused onClick={togglePlayMock} />
<Media title='secondMedia' paused onClick={togglePlayMock} />
</div>,
);

fireEvent.click(container.getByTitle('firstMedia'));
expect(togglePlayMock).toHaveBeenCalledTimes(1);
currentMedia = container.getByTitle('firstMedia');
expect(currentMedia.textContent).toMatch(/Playing/);

fireEvent.click(container.getByTitle('secondMedia'));
expect(togglePlayMock).toHaveBeenCalledTimes(2);
currentMedia = container.getByTitle('secondMedia');
expect(currentMedia.textContent).toMatch(/Playing/);
});
});
33 changes: 26 additions & 7 deletions app/javascript/mastodon/features/audio/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { formatTime, getPointerPosition, fileNameFromURL } from 'mastodon/featur

import { Blurhash } from '../../components/blurhash';
import { displayMedia, useBlurhash } from '../../initial_state';
import { currentMedia, setCurrentMedia } from '../../reducers/media_attachments';

import Visualizer from './visualizer';

Expand Down Expand Up @@ -165,15 +166,32 @@ class Audio extends PureComponent {
}

togglePlay = () => {
if (!this.audioContext) {
this._initAudioContext();
}
const audios = document.querySelectorAll('audio');

audios.forEach((audio) => {
const button = audio.previousElementSibling;
button.addEventListener('click', () => {
if(audio.paused) {
audios.forEach((e) => {
if (e !== audio) {
e.pause();
}
});
audio.play();
this.setState({ paused: false });
} else {
audio.pause();
this.setState({ paused: true });
}
});
});

if (this.state.paused) {
this.setState({ paused: false }, () => this.audio.play());
} else {
this.setState({ paused: true }, () => this.audio.pause());
if (currentMedia !== null) {
currentMedia.pause();
}

this.audio.play();
setCurrentMedia(this.audio);
};

handleResize = debounce(() => {
Expand All @@ -195,6 +213,7 @@ class Audio extends PureComponent {
};

handlePause = () => {
this.audio.pause();
this.setState({ paused: true });

if (this.audioContext) {
Expand Down
31 changes: 27 additions & 4 deletions app/javascript/mastodon/features/video/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Icon } from 'mastodon/components/icon';
import { playerSettings } from 'mastodon/settings';

import { displayMedia, useBlurhash } from '../../initial_state';
import { currentMedia, setCurrentMedia } from '../../reducers/media_attachments';
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';

const messages = defineMessages({
Expand Down Expand Up @@ -181,6 +182,7 @@ class Video extends PureComponent {
};

handlePause = () => {
this.video.pause();
this.setState({ paused: true });
};

Expand Down Expand Up @@ -344,11 +346,32 @@ class Video extends PureComponent {
};

togglePlay = () => {
if (this.state.paused) {
this.setState({ paused: false }, () => this.video.play());
} else {
this.setState({ paused: true }, () => this.video.pause());
const videos = document.querySelectorAll('video');

videos.forEach((video) => {
const button = video.nextElementSibling;
button.addEventListener('click', () => {
if (video.paused) {
videos.forEach((e) => {
if (e !== video) {
e.pause();
}
});
video.play();
this.setState({ paused: false });
} else {
video.pause();
this.setState({ paused: true });
}
});
});

if (currentMedia !== null) {
currentMedia.pause();
}

this.video.play();
setCurrentMedia(this.video);
};

toggleFullscreen = () => {
Expand Down
7 changes: 7 additions & 0 deletions app/javascript/mastodon/reducers/media_attachments.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { Map as ImmutableMap } from 'immutable';

import { STORE_HYDRATE } from '../actions/store';

export let currentMedia = null;

export function setCurrentMedia(value) {
currentMedia = value;
}


const initialState = ImmutableMap({
accept_content_types: [],
});
Expand Down

0 comments on commit d3cd37d

Please sign in to comment.