Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Audio Playback #576

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

csponge
Copy link

@csponge csponge commented Nov 6, 2024

Add the ability to play audio files.

Add a slider to seek through an audio file.

Add play/pause and mute/unmute buttons for audio files.

Note: This is a continuation of a mistakenly closed PR:
Ref: #529

While redoing the changes, I made a couple of improvements. When the end of the track is reached, the pause button will swap to the play button and allow the track to be replayed.

Here is the original feature request:
Ref: #450

Add the ability to play audio files.

Add a slider to seek through an audio file.

Add play/pause and mute/unmute buttons for audio files.

Note: This is a continuation of a mistakenly closed PR:
Ref: TagStudioDev#529

While redoing the changes, I made a couple of improvements.
When the end of the track is reached, the pause button will
swap to the play button and allow the track to be replayed.

Here is the original feature request:
Ref: TagStudioDev#450
@CyanVoxel CyanVoxel added Type: UI/UX User interface and/or user experience Priority: Medium An issue that shouldn't be be saved for last Status: Review Needed A review of this is needed labels Nov 7, 2024
@CyanVoxel CyanVoxel added this to the Alpha v9.5 (Post-SQL) milestone Nov 7, 2024
Copy link
Collaborator

@VasigaranAndAngel VasigaranAndAngel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It starts playing the audio once it's loaded, even when the player is paused (pause button is visible). Please fix this by preventing it from autoplaying when the player is paused. Also, make sure the code is formatted with Ruff and passes MyPy checks. (Install pre-commit if you haven’t already.)

@csponge
Copy link
Author

csponge commented Nov 13, 2024

Ah, nice catch! I'll get working on that.

Copy link
Collaborator

@VasigaranAndAngel VasigaranAndAngel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s do some quick fix/improve the things that caught my eye.

Comment on lines 9 to 12
from PySide6.QtCore import (
Qt,
QUrl,
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from PySide6.QtCore import (
Qt,
QUrl,
)
from PySide6.QtCore import Qt, QUrl

Comment on lines 32 to 35
self.base_size: tuple[int, int] = (266, 75)

self.setMinimumSize(*self.base_size)
self.setMaximumSize(*self.base_size)
Copy link
Collaborator

@VasigaranAndAngel VasigaranAndAngel Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to set the size explicitly? this widget is added to a QSplitter and that will automatically adjust the size of its child widgets. setting the size of this widget causes all the widgets in that QSplitter to follow the same fixed width. i'm not sure why but my assumption is that in a QSplitter the layout of child widgets is typically controlled by the available space and stretch factors of each widget. when we set one widget's width to fixed size, the QSplitter tries to respect that width when distributing the remaining space among the other widgets.

btw, using setFixedSize() is basically the same as using both setMinimumSize() and setMaximumSize() if you weren't aware.

Suggested change
self.base_size: tuple[int, int] = (266, 75)
self.setMinimumSize(*self.base_size)
self.setMaximumSize(*self.base_size)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree setting the size explicitly isn't the best approach (didn't know it would have those side effects). I think I'm probably going to have to look into a different way to size the widget though.

When I remove the min/max size, the audio widget grows/shrinks as expected. However, after a certain point the UI looks overstretched. I'll do some more research soon.

Also open to suggestions if you think of any.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you help me to reproduce that issue?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. If you apply your change and resize the audio player widget, the buttons/slider will space out (note: I've made a few UI tweaks).

This is normal:
Screenshot_20241115_160547

This is after resizing the audio player widget:
Screenshot_20241115_160338

I think it would be nice if the buttons stay more compact. Thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I just do setFixedHeight() on the audio player?

Not sure how I feel about it, but I don't think it would affect the other widgets in the preview panel.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is the expected behavior. the self.base_layout (QVBoxLayout) stretches the widgets as the AudioPlayer widget resizes.
and yes, using setFixedHeight() will fix that. i didn’t notice any issues with other widgets in the preview panel when setting a fixed height.

Comment on lines 172 to 175
self.player.setSource(QUrl().fromLocalFile(self.filepath))
self.play()
else:
self.player.setSource(QUrl().fromLocalFile(self.filepath))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QUrl.fromLocalFile is a staticmethod.

Suggested change
self.player.setSource(QUrl().fromLocalFile(self.filepath))
self.play()
else:
self.player.setSource(QUrl().fromLocalFile(self.filepath))
self.player.setSource(QUrl.fromLocalFile(self.filepath))
self.play()
else:
self.player.setSource(QUrl.fromLocalFile(self.filepath))

Comment on lines 163 to 166
def close(self, *args, **kwargs) -> bool:
self.player.stop()
return super().close(*args, **kwargs)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of overriding the close method of QWidget? this method is used to handle the closing process when the widget is opened as a window right?

Suggested change
def close(self, *args, **kwargs) -> bool:
self.player.stop()
return super().close(*args, **kwargs)

@csponge
Copy link
Author

csponge commented Nov 15, 2024

@VasigaranAndAngel, is it preferred that I commit your suggestions in github or make the changes locally?

@VasigaranAndAngel
Copy link
Collaborator

@VasigaranAndAngel, is it preferred that I commit your suggestions in github or make the changes locally?

i’ve suggested changes because it’s easier for you, but if you prefer making changes locally, feel free to do so and commit them. it’s up to you.

@CyanVoxel CyanVoxel removed the Status: Review Needed A review of this is needed label Nov 15, 2024
@seakrueger
Copy link
Collaborator

This is a much requested feature and necessary for parity with videos, thanks!
Personally, I feel there's a lot a potential duplication between this and the video playback widget. Features like pause, play, and mute exist in both, but currently diverge in appearance and implementation. Audio playback will now have scrubbing and volume control, which would be great features for the video player also. I feel there's opportunity to reduce this duplication, especially seeing as both are based around QMultimediaPlayer. Perhaps adapting video_player into multimedia_player or perhaps subclassing from a common mutltimedia_widget?
I don't want to tack too much into this pr or force too much creep, but just my two cents on this. Once again thanks.

@csponge
Copy link
Author

csponge commented Nov 15, 2024

@seakrueger, I see what you mean. There is a lot of duplication, and I agree that the two should probably me merged.

My original intent was to start out small with a separate widget, and eventually suggest merging the audio/video player. I think I would be okay trying the merge in this PR, but I would like to get some feedback in terms of UI/Implementation before attempting to merge.

@CyanVoxel and @VasigaranAndAngel, any thoughts?

@VasigaranAndAngel
Copy link
Collaborator

as seakrueger suggested, subclassing a multimedia widget is what i’d recommend as well. using the same QMediaPlayer and QAudioOutput also would be nice. i don’t have any additional suggestions atm, but i’ll update if anything comes to mind.

@CyanVoxel
Copy link
Member

Having a unified player would certainly be nice, but would take second priority behind having any audio player at all in the meantime. As for specific UI suggestions, I feel that Chrome's native media player always did a good job of staying minimal while giving you a decent basic control set, so you wouldn't go wrong trying to emulate a similar UI/UX to that. For audio files, I feel we have the advantage of the waveform and album art thumbnails which could be displayed in place of where a video would otherwise be.

Added a MediaPlayer base class per some suggestions
in the PR comments. Hopefully this reduces duplicate code
between the audio/video player in the future.
@csponge
Copy link
Author

csponge commented Nov 18, 2024

Okay, I did some work and ended up with a MediaPlayer base class. It obviously isn't being used by the video player at the moment, but I think it could be a good start.

Let me know of any thoughts or suggestions. Thank you all!

@VasigaranAndAngel
Copy link
Collaborator

wow the new audio player widget looks awesome 😍

i haven’t dived deep into the code, but here are a few things that caught my eye:
the play and volume button, slider, and similar stuff should probably go into the MediaPlayer class so they can be used for the video player also.
consider using time.gmtime() to convert the time into human friendly format.

@csponge
Copy link
Author

csponge commented Nov 20, 2024

@VasigaranAndAngel, thanks!

I updated the code to use time.gmtime and time.strftime. Thanks for that suggestion.

On moving the media controls to the MediaPlayer base class. I wonder if that is too much for a base class. At that point, I feel like it would make more sense to just make one MediaPlayer class for both video and audio. For now, I could move the controls to MediaPlayer and just get rid of the AudioPlayer class. Then, work on porting over the video player later.

Any thoughts on this?

I appreciate your time reviewing this with me.

@VasigaranAndAngel
Copy link
Collaborator

I wonder if that is too much for a base class. At that point, I feel like it would make more sense to just make one MediaPlayer class for both video and audio.

isn’t that the goal of creating a base class? if we add media control buttons to both the video player and audio player separately, it would still involve duplication, right? but it’s okay to duplicate if the control buttons for video and audio players are intended to be different.

For now, I could move the controls to MediaPlayer and just get rid of the AudioPlayer class.

I'm okay with that idea. but i'm not sure if it's possible to make the media player efficiently show thumbnail if it's playing audio and show the video if it's playing video. we can adjust it as needed when porting the video player. so it’s fine for now.

Move media controls from the AudioPlayer widget
to the MediaPlayer base class. This removes the need
for a separate AudioPlayer class, and allows the
video player to reuse the media controls.
Update the position_label when the slider is moving.
@csponge
Copy link
Author

csponge commented Nov 22, 2024

isn’t that the goal of creating a base class? if we add media control buttons to both the video player and audio player separately, it would still involve duplication, right?

That's a fair point. With the most recent commit, I've added the media controls to the base MediaPlayer class.

Also, I made another commit to fix an issue with the player position label. Before, it wouldn't update when the slider was being moved. Now, it should update with the slider.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority: Medium An issue that shouldn't be be saved for last Type: UI/UX User interface and/or user experience
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

4 participants