15. 媒体播放器

一、播放压缩音频

  MediaPlayer 是 QML 提供的核心多媒体类,可以用来播放压缩音频或者视频。要使用 MediaPlayer,需要引入 QtMultimedia 模块,在 QML 文档的开始加入 import QtMultimedia 语句。

  我们可以在终端中使用 pip 安装 PySide6 模块。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip 指令后通过 -i 指定国内镜像源下载

pip install pyside6 -i https://mirrors.aliyun.com/pypi/simple

  国内常用的 pip 下载源列表:

  MediaPlayer 类型可以用来播放压缩音频或者视频。要播放音频,需要为 MediaPlayeraudioOutput 属性设置一个 AudioOutput 对象,常用的属性如下:

device : AudioDevice                                                            // 此输出所连接的音频设备
volume : real                                                                   // 音量的大小,取值范围是0.0~1.0,默认值是1.0。
muted : bool                                                                    // 是否静音,默认值是false

  MediaPlayer 常用属性如下:

audioOutput : AudioOutput                                                       // 音频输出设备
source : url                                                                    // 音频文件的路径
autoPlay : bool                                                                 // 是否自动播放,默认值是false
loops : int                                                                     // 播放循环次数,默认值是1
duration : int                                                                  // 音频的持续时间
position : int                                                                  // 当前播放位置,单位是毫秒
playbackRate : real                                                             // 播放速率,默认值是1.0
playbackState : enumeration                                                     // 播放状态
metaData : mediaMetaData                                                        // 音频文件的元数据

  我们可以通过 MediaPlayersource 属性 指定音频文件的路径,它的类型是 url,它能接受绝对路径、相对路径、有效的 http 链接。通过 loops 属性可以 设置播放的循环次数,当设置为 01只会播放一次,当设置为 MediaPlayer::Infinite 时会 无限循环播放,其默认值为 1

  我们可以使用 playbackState 属性 获取播放状态,它是一个枚举值,可以取值如下:

PlayingState                                                                    // 正在播放状态
PausedState                                                                     // 暂停状态
StoppedState                                                                    // 停止状态

  我们可以通过 MediaPlayerplay()pause()stop() 等方法进行 播放暂停停止 等操作。当进行完这些操作时会发射 playbackStateChanged() 信号,如果发生错误,会发射 errorOccurred() 信号。

  新建一个 template.py 文件。

import sys

from PySide6.QtWidgets import QApplication
from PySide6.QtQml import QQmlApplicationEngine

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    engine = QQmlApplicationEngine()                                            # 2.创建QML引擎对象
    engine.load("template.qml")                                                 # 3.加载QML文件
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

  新建一个 template.qml 文件。

import QtQuick.Window
import QtQuick.Controls
import QtQuick.Dialogs
import QtMultimedia

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 400                                                                  // 窗口的宽度
    height: 100                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色

    MediaPlayer {
        id: mediaPlayerId

        audioOutput: AudioOutput {
            id: audioOutputId

            volume: volumeSliderId.value / 100                                  // 音量范围0.0-1.0
            muted: muteCheckBoxId.checked                                       // 是否静音
        }
    }

    Column {
        anchors.centerIn: parent

        padding: 10
        spacing: 10

        Text {
            id: textId
            text: "请选择文件"
        }

        Row {
            spacing: 10

            Text {
                text: "音量"
            }

            Slider {
                id: volumeSliderId
                width: 300
                from: 0.0
                to: 100
                stepSize: 1
                value: 80
            }
        }

        Row {
            spacing: 10

            Button {
                width: 90
                text: "选择音频文件"

                onClicked: {
                    fileDialogId.open()                                         // 打开文件对话框
                }
            }

            Button {
                id: playButtonId
                width: 90
                enabled: false
                text: "开始播放"

                onClicked: {
                    if (mediaPlayerId.source == "") {
                        textId.text = "请先选择音频文件"
                        return
                    } else if (playButtonId.text == "开始播放" || playButtonId.text == "继续播放") {
                        mediaPlayerId.play()                                    // 播放音频
                        playButtonId.text = "暂停播放"
                        stopButtonId.enabled = true
                    } else if (playButtonId.text == "暂停播放") {
                        mediaPlayerId.pause()                                   // 暂停播放
                        playButtonId.text = "继续播放"
                    }
                }
            }

            Button {
                id: stopButtonId
                width: 90
                enabled: false
                text: "停止播放"

                onClicked: {
                    if (mediaPlayerId.source == "") {
                        textId.text = "请先选择音频文件"
                        return
                    } else {
                        mediaPlayerId.stop()                                   // 停止播放
                        playButtonId.text = "开始播放"
                        stopButtonId.enabled = false
                    }
                }
            }

            CheckBox {
                id: muteCheckBoxId
                text: "是否静音"
            }
        }
    }

    // 文件对话框
    FileDialog {
        id: fileDialogId

        title: "选择音频文件"                                                    // 文件对话框的标题
        nameFilters: ["音频文件(*.wav *.flac *.mp3)"]                           // 文件对话框的文件类型过滤器

        onAccepted: {
            // FileDialog的selectedFile保存了用户选择的文件路径,如果是多个文件,则显示第一个
            var names = selectedFile.toString().split("/")
            var fileName = names[names.length - 1]
            textId.text = "你选择了【" + fileName + "】音频文件"
            mediaPlayerId.stop()                                                // 停止播放
            mediaPlayerId.source = selectedFile                                 // 设置音频文件路径
            playButtonId.text = "开始播放"
            playButtonId.enabled = true
            stopButtonId.enabled = false
        }
    }
}

二、播放无损音频

  我们可以使用 SoundEffect 类型通过 低延迟 的方式播放 未压缩 的音频文件。如果不需要低延迟,那么建议使用 MediaPlayer 类型。一般 SoundEffect 类型播放的声音都是会被多次使用的。它允许提前进行解析并且准备完毕,在需要的时候只需要触发即可。SoundEffect 的常用属性如下:

source : url                                                                    // 音频文件的路径
volume : real                                                                   // 音量的大小,取值范围是0.0~1.0,默认值是1.0
playing : bool                                                                  // 是否正在播放,默认值是false
muted : bool                                                                    // 是否静音,默认值是false
loops : int                                                                     // 播放循环次数,默认值是1
loopsRemaining : int                                                            // 播放循环次数剩余,默认值是loops
status : enumeration                                                            // 播放状态

  我们可以通过 SoundEffectplay()stop() 等方法进行 播放停止 等操作。

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls
import QtQuick.Dialogs
import QtMultimedia

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 300                                                                  // 窗口的宽度
    height: 100                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色

    SoundEffect {
        id: soundEffectId
        muted: muteCheckBoxId.checked
        volume: volumeSliderId.value / 100
    }

    Column {
        anchors.centerIn: parent

        padding: 10
        spacing: 10

        Text {
            id: textId
            text: "请选择文件"
        }

        Row {
            spacing: 10

            Text {
                text: "音量"
            }

            Slider {
                id: volumeSliderId
                width: 240
                from: 0.0
                to: 100
                stepSize: 1
                value: 80
            }
        }

        Row {
            spacing: 10

            Button {
                width: 90
                text: "选择音频文件"

                onClicked: {
                    fileDialogId.open()                                         // 打开文件对话框
                }
            }

            Button {
                id: playButtonId
                width: 90
                enabled: false
                text: "开始播放"

                onClicked: {
                    if (soundEffectId.source == "") {
                        textId.text = "请先选择音频文件"
                        return
                    } else if (playButtonId.text == "开始播放") {
                        soundEffectId.play()                                    // 播放音频
                        playButtonId.text = "停止播放"
                    } else if (playButtonId.text == "停止播放") {
                        soundEffectId.stop()                                    // 停止播放
                        playButtonId.text = "开始播放"
                    }
                }
            }

            CheckBox {
                id: muteCheckBoxId
                text: "是否静音"
            }
        }
    }

    // 文件对话框
    FileDialog {
        id: fileDialogId

        title: "选择音频文件"                                                    // 文件对话框的标题
        nameFilters: ["音频文件(*.wav)"]                                        // 文件对话框的文件类型过滤器

        onAccepted: {
            // FileDialog的selectedFile保存了用户选择的文件路径,如果是多个文件,则显示第一个
            var names = selectedFile.toString().split("/")
            var fileName = names[names.length - 1]
            textId.text = "你选择了【" + fileName + "】音频文件"
            soundEffectId.stop()                                                // 停止播放
            soundEffectId.source = selectedFile                                 // 设置音频文件路径
            playButtonId.text = "开始播放"
            playButtonId.enabled = true
        }
    }
}

三、视频播放

  要使用 MediaPlayer 播放视频,除了 指定音频输出,还需要使用 videoOutput 属性 指定视频输出对象 VideoOutputVideoOutput 类型中有一个 orientation 属性,可以 设置视频的方向,通过指定一个度数(需要是 90 的倍数)来旋转视频,逆时针方向为正值。另外,该类型还包含一个 fillMode 属性,用来定义 视频如何缩放来适应窗口,它是一个枚举值,可以取值如下:

VideoOutput.PreserveAspectFit                                                   // 默认值,视频宽高按比例进行缩放,不会进行裁剪
VideoOutput.PreserveAspectCrop                                                  // 视频宽高按比例进行缩放,在必要时会进行裁剪
VideoOutput.Stretch                                                             // 视频进行缩放来适应窗口大小

  修改 template.qml 文件的内容。

import QtQuick.Window
import QtQuick.Controls
import QtQuick.Dialogs
import QtQuick.Layouts
import QtMultimedia

// Window控件表示一个顶级窗口
// 在QML中,元素是通过大括号{}内的属性来配置的。
Window {
    width: 800                                                                  // 窗口的宽度
    height: 600                                                                 // 窗口的高度
    visible: true                                                               // 显示窗口
    color: "lightgray"                                                          // 窗口的背景颜色

    MediaPlayer {
        id: mediaPlayerId

        audioOutput: AudioOutput {
            id: audioOutputId

            volume: volumeSliderId.value / 100                                  // 音量范围0.0-1.0
            muted: muteCheckBoxId.checked                                       // 是否静音
        }

        videoOutput: videoOutputId                                              // 视频输出
        playbackRate: Number(speedComboBoxId.currentText)                       // 播放速度
    }
    
    ColumnLayout {
        anchors.fill: parent

        VideoOutput {
            id: videoOutputId

            Layout.fillWidth: true
            Layout.fillHeight: true

            fillMode: VideoOutput.PreserveAspectCrop                            // 视频输出模式
        }

        RowLayout {
            spacing: 10

            Text {
                text: "进度"

                Layout.leftMargin : 10
            }

            Slider {
                id: progressSliderId

                Layout.fillWidth: true
                Layout.rightMargin : 10

                from: 0
                to: mediaPlayerId.duration
                value: mediaPlayerId.position
                enabled: false

                onMoved: {
                    mediaPlayerId.position = value
                }
            }
        }

        Row {
            padding: 10
            spacing: 10

            Button {
                width: 90
                text: "选择视频文件"

                onClicked: {
                    fileDialogId.open()                                         // 打开文件对话框
                }
            }

            Button {
                id: playButtonId
                width: 90
                enabled: false
                text: "开始播放"

                onClicked: {
                    if (mediaPlayerId.source == "") {
                        textId.text = "请先选择音频文件"
                        return
                    } else if (playButtonId.text == "开始播放" || playButtonId.text == "继续播放") {
                        mediaPlayerId.play()                                    // 播放音频
                        playButtonId.text = "暂停播放"
                        stopButtonId.enabled = true
                        progressSliderId.enabled = true
                    } else if (playButtonId.text == "暂停播放") {
                        mediaPlayerId.pause()                                   // 暂停播放
                        playButtonId.text = "继续播放"
                    }
                }
            }

            Button {
                id: stopButtonId
                width: 90
                enabled: false
                text: "停止播放"

                onClicked: {
                    mediaPlayerId.stop()                                        // 停止播放
                    playButtonId.text = "开始播放"
                    stopButtonId.enabled = false
                    progressSliderId.enabled = false
                }
            }

            Text {
                text: "倍数"
            }

            ComboBox {
                id: speedComboBoxId
            
                model: ["0.25", "0.5", "0.75", "1.0", "1.25", "1.5", "1.75", "2.0"]
                currentIndex: 3
            }

            CheckBox {
                id: muteCheckBoxId
                text: "是否静音"
            }

            Text {
                text: "音量"
            }

            Slider {
                id: volumeSliderId
                width: 100
                from: 0.0
                to: 100
                stepSize: 1
                value: 80
            }
        }
    }

    // 文件对话框
    FileDialog {
        id: fileDialogId

        title: "选择视频文件"                                                    // 文件对话框的标题
        nameFilters: ["视频文件(*.mp4 *.ts)"]                                    // 文件对话框的文件类型过滤器

        onAccepted: {
            // FileDialog的selectedFile保存了用户选择的文件路径,如果是多个文件,则显示第一个
            mediaPlayerId.stop()                                                // 停止播放
            mediaPlayerId.source = selectedFile                                 // 设置音频文件路径
            playButtonId.text = "开始播放"
            playButtonId.enabled = true
            stopButtonId.enabled = false
            progressSliderId.enabled = false
        }
    }
}
posted @ 2025-10-13 22:22  星光映梦  阅读(6)  评论(0)    收藏  举报