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

新的 BesSlider 和更好用的 MusicPlayer #47

Merged

Conversation

pzhlkj6612
Copy link
Member

@pzhlkj6612 pzhlkj6612 commented Jan 11, 2020

简述

本 PR 重写了BesSlider和相关界面逻辑,解决了播放进度条在使用上的主要问题。本 PR 还修改了部分播放逻辑,试图让程序运行得更好(properly)。

本 PR 试图解决 #12#13#15#16#17#19#20#21#23#31#32#38#39#41#42 中提到或遗留的问题。

本 PR 在 Ubuntu 18.04 / Windows 10 1903 / macOS 10.14[1] 测试通过。

详细内容

1. 移除前 2 秒的噪音

来自 #41

2. 不初始化SDL_INIT_TIMERSDL_INIT_VIDEO子系统

来自 #42

3. 判定音频到达尾部的更好的方法

#38 (comment) 提到。

4. 移除 1 亿(1e8)次尝试并修改了audio_decode_frame()packet_queue_get()中的逻辑

#23#38 提到。
本 PR 对于 #23#38 的解决不完全,特别是 #38 (comment) 提到的问题,这可能需要重新设计PlayThread

4.1. 移除 1 亿(1e8)次尝试

对于音频是否到达尾部有更好的方法,见上文。

4.2. 修改audio_decode_frame()packet_queue_get()

这部分修改是以我自己的理解来做的,目的是修 BUG,并且让逻辑更清楚。但目前我还没有掌握 FFmpeg 的基本用法,所以可能有不合理之处。

  • 如果PacketQueue的第一个AVPacket包是nullptr,同时block置为false,那么packet_queue_get()将会返回-1
  • 除非重采样(SWR 相关)进行顺利,返回正确的音频数据,其他情况下,audio_decode_frame()都将返回-1
  • 现在,m_MS.audio_clock将以qint64类型存储当前音频位置的毫秒时间。相关的方法的形参类型、返回值类型也做了修改,但尽量把影响控制在最小范围——数据类型的统一之后再说。

5. 使用新的BesSlider并对BottomWidgetMusicPlayer做适配

#32 改良。

5.1. 新的BesSlider

  • 点击 = 在手柄上点击
    • 当鼠标点击并拖动手柄时,鼠标与手柄中心的相对位置将会被计算并存储到click_pos_offset,这样,手柄就会相对于鼠标保持一定的偏移量进行运动,而不是把自己的中心放到鼠标下方。这是 网易云音乐 的风格。
  • 点击 = 在手柄以外的地方点击
    • 使用BesSliderStyle改变定位方式,原来是“步进”,现在是“直接”。
    • 新的定位方式将会使“点击并拖动”操作能够正确执行,因为在点击时QSlider::sliderPressedQSlider::sliderMovedQSlider::sliderReleased信号将会被正确地发送。
  • “我的鼠标在哪儿?”
    • 使用subControlRect确定手柄的范围,进而确定鼠标是否在手柄上。
    • 包括enterEvent(QEvent*)leaveEvent(QEvent*)方法在内的旧逻辑被移除了。
    • 设置setMouseTracking(true)。当enableMouseEvt置为true时,无论鼠标是否按下,当鼠标悬停在手柄上时,它的指针样式都将为Qt::PointingHandCursor,否则将为Qt::ArrowCursor(由unsetCursor()设置)。

5.2. 对BottomWidgetMusicPlayer做适配

  • 关于优化的逻辑
    • 即使仅仅是在手柄上做了一次点击(而不拖动),seek 操作也会被执行,所以移除了audioOriginalPos。这是 网易云音乐 的风格。
    • 现有的逻辑限制了仅当enableMouseEvt置为true时对sliderSong的点击才有效,所以冗余的判断被移除了。
  • 正在播放的音频的位置
    • sliderSong被按下时,AdjustingPos将被置为true
    • 当 seek 操作完成或出错时,MusicPlayer将会通过信号MusicPlayer::seekFinished()MusicPlayer::seekError()通知BottomWidgetAdjustingPos将被置为false
    • 这样能保证仅当没有鼠标操作作用于sliderSong时,音频的当前位置才能被MusicPlayer刷新。

6. 移除m_positionUpdateTimer并通过audio_decode_frame()发出位置变化的信号

  • 任何 interval 的值都不是合理的,过高的值会发送太多相同的音频位置,而太低的值会让位置不准确。
  • 现在,当PlayThread::audio_decode_frame()被调用时,PlayThread将会提醒MusicPlayer以发送新的音频位置。
  • PlayThread::positionChanged()信号不包含当前位置的值。
  • MusicPlayer收到PlayThread::positionChanged()信号时,MusicPlayer::sendPosChangedSignal()方法将会被调用。
  • MusicPlayer::positionChanged(qint64)信号包含了由playThread->getCurrentTime()获得的位置。
  • MusicPlayer::seek(quint64)方法中关于定时器的操作是无用且有误导性的(在 在 seek 时移除多余的对定时器的操作 #31 提到)。

7. 当PlayThread处于AGS_SEEK状态时不进行pauseDevice()playDevice()

7.1. 移除pauseDevice()

如果本身是在暂停状态下,这个调用就是多余的。

7.2. 移除playDevice()

实现“暂停时 seek 后继续暂停”。

在暂停时,通过代码(programmatically)进行 seek 后,音频位置将会即时刷新。

8. 提供回退 seek

  • 方法av_seek_frame()使用AVSEEK_FLAG_ANY方式进行 seek 时可能会出错,特别是 seek 到音频末尾附近时。
  • 所以,播放器将尝试使用AVSEEK_FLAG_BACKWARD方式再 seek 一次。

脚注

1. ^ macOS 需要 #37

It seems that the responding speed of the audio playing has been improved.
It seems that `read_header()` in av_format.h is the source of the noise.
branch name: remove_useless_initialization_of_SDL_subsystem
The "PlayThread's logic" are the functions `audio_decode_frame()` and `packet_queue_get()`.

1. `audio_clock` will store the position in millisecond and `qint64` type.
2. the 1e8 times attempts is NOT ok.
3. `packet_queue_get()` should return `-1` if something goes wrong.
1. New `BesSlider`
    1.1. Click is on the handle
        1.1.1. The relative position between the mouse and the center of handle (`click_pos_offset`) is calculated and stored when the handle is dragged. So the handle will move relative to the mouse and keep a certain offset (`click_pos_offset`) instead of placing its center under the mouse. This is a Netease Music style.
    1.2. Click is NOT on the handle
        1.2.1. Use `BesSliderStyle` to change the positioning method from 'stepping' to 'directness'.
        1.2.2. The new positioning method makes the click-and-drag operation can be performed correctly, because `QSlider::sliderPressed`, `QSlider::sliderMoved` and `QSlider::sliderReleased` signals will be emitted correctly when the click is not on the handle.
    1.3. "Where is my mouse ?"
        1.3.1. Use `subControlRect` to determine the range of handle and whether the mouse is over the handle.
        1.3.2. Remove old logic related to the above, including `enterEvent(QEvent*)` and `leaveEvent(QEvent*)` functions.
        1.3.3. Set `setMouseTracking(true)`. As long as `enableMouseEvt` is equal to `true`, whether or not the mouse is pressed, when the mouse hovers over the handle, its cursor style will be `Qt::PointingHandCursor`, otherwise it will be `Qt::ArrowCursor` (set by `unsetCursor()`).

2. `BottomWidget` and `MusicPlayer`
    2.1. About the refined logic
        2.1.1. Even if only the handle of `sliderSong` is clicked, the seeking operation will be performed. `audioOriginalPos` is dead. This is a Netease Music style.
        2.1.2. Existing logic restricts clicks to be performed on `sliderSong` only when `enableMouseEvt` is equal to `true`, so the redundant judgments have been removed.
    2.2. The position of the audio is playing
        2.2.1. `AdjustingPos` will be set `true` when `sliderSong` is pushed down.
        2.2.2. `MusicPlayer` will tell `BottomWidget` and `AdjustingPos` will be set `false` when the seeking operation is finished (`PlayThread::seekFinished()` and `MusicPlayer::seekFinished()`).
        2.2.3. So, It's guaranteed that the current position will only be refreshed by `MusicPlayer` if no mouse action is applied to `sliderSong`.
1. `BottomWidget` will reset `AdjustingPos` to `false` and ready for next seeking operation.
2. End the player by setting `AGSStatus` to `AGS_FINISH`.
1. Any interval of the timer is unreasonable. Higher values send too many indentical positions, while lower values make the positions inaccurate.
2. Now when the `PlayThread::audio_decode_frame()` function is called, `PlayThread` will notify `MusicPlayer` to send the position.
3. `PlayThread::positionChanged()` signal does not contain the value of the current position.
4. When `MusicPlayer` receives the signal `PlayThread::positionChanged()`, the `MusicPlayer::sendPosChangedSignal()` function will be called.
5. The `MusicPlayer::positionChanged(qint64)` signal contains the position which obtained from `playThread->getCurrentTime()`.
6. The pause-and-play operation of the timer in function `MusicPlayer::seek(quint64)` is useless and misleading.
1. If the player is paused, this call is useless.
1. Implement the keeping pause after seeking.
2. When the player is paused, the new position should be sent on time after a seeking which is performed by the program.
1. The function `av_seek_frame()` will get error with `AVSEEK_FLAG_ANY` method, especially when somebody wants to seek to the end of the audio.
2. So, the player will try seeking again with `AVSEEK_FLAG_BACKWARD` method.
@pzhlkj6612

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
_Refactor the player 整个播放逻辑需要重做。
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants