基于QML的播放器实现

最近这段时间在学习QML,期间阅读了安晓辉的《Qt Quick核心编程》,书写的很好,但是啃起来很费劲,书籍读起来的确比较枯燥,所以后面直接采用了前人的实际编程代码来进行学习,遇到不理解的再查阅书籍和相关的帮助文档,最后有了这个播放器的小Demo,可供初学者快速入门的学习之用,实现了目前线上比较主流的播放器涉及的一些功能和方法。播放器的实现是在参考了很多人的编写代码的基础上进行重写的,程序的主界面重写了标题栏,添加了右键菜单的功能,按钮的图标全部用文字代替(因为懒得找图片了),后期有兴趣的可以继续扩展修改下去。


此外播放器还添加的播放器设置窗口,窗体弹出采用了特殊的动画效果(Qt实现的动画效果还是很酷炫的,哈哈),弹出的配置窗体自动在程序主界面生成蒙层,主界面此时不可控,只有设置窗体关闭后主窗体才能响应用户操作。设置窗体只实现了一个整体框架,具体的配置功能方面,有兴趣的可以继续完善下去。


项目结构


下面是程序的主代码,实现的相关功能都有相应的注释,方便各位初学者理解学习。

main.qml

import QtQuick 2.0
import QtMultimedia 5.0
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.2
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import QtQuick.Window 2.2


Window{
    id:myPlayer
    width: 1024
    height: 650
    visible: true
    title:"MyPlayer:" + getVedioName(fd.fileUrl.toString())
    flags: Qt.Window | Qt.FramelessWindowHint
    property int isMaxStatus //存储窗口是否最大化

    //蒙层
    Mask{
        id: mainWinMask
        visible: false
    }


    //config窗体
    // 弹出层
    ConfigWindow {
        id: cfgWin
        width: 500; height: 250
        x: myPlayer.x + 300; y:myPlayer.y + 150;
        //anchors.centerIn: parent  // 注意:使用位移动画不能用anchors定位方式
        //z: 101
        opacity: 1
        visible: false;
        //radius: 5
        //
        Text{
            text: '--this is myPlayer Config--'
            anchors.bottom: parent.bottom
            anchors.centerIn: parent
        }
        Button {
            id: btnClose
            anchors.horizontalCenter: parent.horizontalCenter
            anchors.bottom: parent.bottom
            anchors.bottomMargin: 10
            text:"Close"
            width: 98
            onClicked: cfgWin.hide()
        }
    }
//    //测试窗体
//    NeedShowWindow{
//        id:needShowWindowPanel
//    }
    //获取影音名称
    function getVedioName(str)
    {
        var url=fd.fileUrl.toString();
        var strList=new Array();
        strList=url.split("/");
        var name=strList[strList.length-1];
        return name;
    }

    //标题栏区域
    Rectangle {
            id: mainTitle                       //创建标题栏
            anchors.top: parent.top             //对标题栏定位
            anchors.left: parent.left
            anchors.right: parent.right
            height: 25                          //设置标题栏高度
            color: "#7B7B7B"                    //设置标题栏背景颜色

            MouseArea { //为窗口添加鼠标事件
                anchors.fill: parent
                acceptedButtons: Qt.LeftButton //只处理鼠标左键
                property point clickPos: "0,0"
                onPressed: { //接收鼠标按下事件
                        clickPos  = Qt.point(mouse.x,mouse.y)
                }
                onPositionChanged: { //鼠标按下后改变位置
                        //鼠标偏移量
                        var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)

                        //如果mainwindow继承自QWidget,用setPos
                        myPlayer.setX(myPlayer.x+delta.x)
                        myPlayer.setY(myPlayer.y+delta.y)
                }
            }
            Row{
                anchors.left: parent.left
                anchors.verticalCenter: parent.verticalCenter
                spacing: 5
                //窗口图片
                Image{
                    source: "qrc:/myPlayerIcon.ico"
                    sourceSize.width: 30
                    sourceSize.height: 25
                }
                //窗口标题
                Text{
                    anchors.top:parent.top
                    anchors.topMargin: 4
                    text: getVedioName(fd.fileUrl.toString()) ? "MyPlayer   Playing:" + getVedioName(fd.fileUrl.toString()) : "MyPlayer"
                    color:"white"
                    font.bold: true
                    font.pixelSize: 15
                }
            }
            Row{
                anchors.right: parent.right
                anchors.top:parent.top
                width:170
                height: 25

                //设置
                Button{
                    id:cfgButton
                    width: 50
                    height: 25
                    style:ButtonStyle{
                        background: Rectangle{
                            border.width: control.hovered ? 2 : 1
                            border.color: (control.hovered || control.pressed) ? "white" : "#7B7B7B"
                            color: (control.hovered || control.pressed) ? "white" : "#7B7B7B"
                            Text {
                                id: cfgButtonTxt
                                text: qsTr("Config")
                                anchors.centerIn: parent
                                color: "black"
                            }
                        }
                    }
                    onClicked: {
                        mainWinMask.visible = true;
                        //animationType: ["fade", "width", "height", "size", "flyDown", "flyUp", "flyLeft", "flyRight"]
                        cfgWin.animationType = "fade";
                        cfgWin.show();
                        console.log("myplaer x:" + myPlayer.x + " y:" + myPlayer.y)
                    }
                }

                //最小化
                Button{
                    id:minButton
                    width: 40
                    height: 25
                    style:ButtonStyle{
                        background: Rectangle{
                            border.width: control.hovered ? 2 : 1
                            border.color: (control.hovered || control.pressed) ? "white" : "#7B7B7B"
                            color: (control.hovered || control.pressed) ? "white" : "#7B7B7B"
                            Text {
                                id: minButtonTxt
                                text: qsTr("Min")
                                anchors.centerIn: parent
                                color: "black"
                            }
                        }
                    }
                    onClicked: {
                          myPlayer.visibility = Window.Minimized
                    }
                }
                //最大化
                Button{
                    id:maxButton
                    width: 40
                    height: 25
                    style:ButtonStyle{
                        background: Rectangle{
                            border.width: control.hovered ? 2 : 1
                            border.color: (control.hovered || control.pressed) ? "white" : "#7B7B7B"
                            color: (control.hovered || control.pressed) ? "white" : "#7B7B7B"
                            Text {
                                id: maxButtonTxt
                                text: qsTr("Max")
                                anchors.centerIn: parent
                                color: "black"
                            }
                        }
                    }
                    onClicked: {

                        if(isMaxStatus == 0){
                            myPlayer.visibility = Window.Maximized
                            isMaxStatus = 1;
                        }else{
                            myPlayer.visibility = Window.Windowed
                            isMaxStatus = 0;
                        }
                    }
                }
                //退出
                Button{
                    id:quitButton
                    width: 40
                    height: 25
                    style:ButtonStyle{
                        background: Rectangle{
                            border.width: control.hovered ? 2 : 1
                            border.color: (control.hovered || control.pressed) ? "red" : "#7B7B7B"
                            color: (control.hovered || control.pressed) ? "red" : "#7B7B7B"
                            Text {
                                id: quitButtonTxt
                                text: qsTr("Quit")
                                anchors.centerIn: parent
                                color: "black"
                            }
                        }
                    }
                    onClicked: {fd.close();  Qt.quit();}
                }
            }
    }
    //主窗体区域
    Column{
        anchors.top:parent.top
        anchors.topMargin: 25
        Rectangle{
            id:screen
            color:"black"
            width:myPlayer.width
            height: myPlayer.height-75

            MouseArea { //为窗口添加鼠标事件
                id: mouseRegion
                anchors.fill: parent;
                acceptedButtons: Qt.LeftButton | Qt.RightButton // 激活右键(别落下这个)

                onClicked: {
                    if (mouse.button === Qt.RightButton) { // 右键菜单
                                //
                        contentMenu.popup()
                     }else if(mouse.button === Qt.LeftButton){A
                        if (player.seekable)
                            playOrpauseButton.clicked();
                    }
                }
            }
            Menu { // 播放器右键菜单
                //title: "Edit"
                id: contentMenu

                MenuItem {
                    text: "Cut"
                    shortcut: "Ctrl+X"
                    onTriggered: {}
                }
            }
            //播放器初始背景图片
            Image{
                id:img
                source: ""
                anchors.fill: parent
            }
            MediaPlayer{
                id:player
                source: fd.fileUrl
                autoPlay: true
                volume: voice.value
            }
            VideoOutput {
                anchors.fill: parent
                source: player
            }
        }
        Rectangle{
            id:control
            color:"#80202020"
            border.color: "gray"
            border.width: 1
            width:myPlayer.width
            height: 20
            Row{
                spacing: 10
                anchors.verticalCenter: parent.verticalCenter
                anchors.leftMargin: 10
                anchors.left: parent.left
                //调节播放速度
                Slider{
                    id:playPos
                    width: myPlayer.width
                    height: 10
                    maximumValue: player.duration
                    minimumValue: 0
                    value:player.position
                    anchors.verticalCenter: parent.verticalCenter
                    stepSize:1000
                    style: SliderStyle {
                        groove: Rectangle {
                            width: myPlayer.width
                            height: 8
                            color: "white"
                            radius: 2
                            Rectangle {
                                id:sliderRect
                                anchors.left: parent.left
                                anchors.top: parent.top
                                anchors.bottom: parent.bottom
                                //进度条进展
                                width: player.duration>0?parent.width*player.position/player.duration:0
                                color: "blue"
                            }
                        }
                        //进度条圆点浮标定制
                        handle: Rectangle {
                            anchors.centerIn: parent
                            color: control.pressed ? "white" : "yellow"
                            border.color: "gray"
                            border.width: 2
                            implicitWidth: 15
                            implicitHeight: 15
                            radius:7.5
                            Rectangle{
                                width: parent.width-8
                                height: width
                                radius: width/2
                                color: "blue"
                                anchors.centerIn: parent
                            }
                        }
                    }
                    //点击鼠标设置播放位置
                    MouseArea {
                        property int pos
                        anchors.fill: parent
                        onClicked: {
                            if (player.seekable)
                                pos = player.duration * mouse.x/parent.width
                            player.seek(pos)
                        }
                    }
//                    onValueChanged: {
//                        var pos
//                        if (player.seekable)
//                            pos = player.position
//                        player.seek(pos)
//                        playPos.value=pos;
//                        playPos.value = player.position
//                        console.log("player.position:" + player.position + "playPos.value" + playPos.value)
//                    }
                }
                Image{
                    width: 15
                    height: 15
                    source: "./Images/voice.png"
                    anchors.verticalCenter: parent.verticalCenter
                }
            }
        }
        //控制区域
        Rectangle{
            id:bottom
            color:"#80202020"
            border.color: "gray"
            border.width: 1
            width: myPlayer.width
            height: 30
            Row{
                anchors.verticalCenter: parent.verticalCenter
                anchors.left: parent.left
                anchors.leftMargin: 10
                spacing: 10
                Button{
                    id:playOrpauseButton
                    width: 60
                    height: 30
                    text: "Start"
                    property int status: 1  //默认播放
                    //iconSource: "./Images/pause.png"
                    onClicked: {
                        if (player.seekable){
                            if(status===1)
                            {
                                player.pause();
                                tooltip="Start";
                                console.log("start")
                                playOrpauseButton.text = "Start"
                                status=0;
                                //iconSource="./Images/play.png"
                            }
                            else{
                                player.play() ;
                                tooltip="Pause";
                                console.log("pause")
                                playOrpauseButton.text = "Pause"
                                status=1;
                                //iconSource="./Images/pause.png"
                            }
                            var pos = player.position
                            player.seek(pos)
                        }
                    }
                }
                Button{
                    width: 60
                    height: 30
                    onClicked:
                    {
                        player.stop()
                        player.seek(0)
                        playOrpauseButton.status = 0
                        playOrpauseButton.text = "Start"
                    }
                    text:"Stop"
                    tooltip: "Stop"
                    //iconSource: "./Images/stop.png"
                }
                //快进快退10s
                Button{
                    width: 60
                    height: 30
                    text: "Back"
                    onClicked: {
                        if (player.seekable){
                        var pos = player.position-10000
                        player.seek(pos)
                        }
                    }
                    tooltip: "Back"
                    //iconSource: "./Images/back.png"
                }
                Button{
                    width: 60
                    height: 30
                    text: "Forward"
                    onClicked: {
                        if (player.seekable){
                        var pos = player.position+10000
                        player.seek(pos)
                        }
                    }
                    tooltip: "Forward"
                    //iconSource: "./Images/pass.png"
                }
                Button{
                    width: 60
                    height: 30
                    tooltip: "Open"
                    text: "Open"
                    onClicked: fd.open()
                    //iconSource: "./Images/add.png"
                    FileDialog{
                        id:fd
                        nameFilters: ["Vedio Files(*.avi *.mp4 *rmvb *.rm)"]  //格式过滤
                        selectMultiple: false
                    }
                }
            }
            Row{
                anchors.verticalCenter: parent.verticalCenter
                anchors.right: parent.right
                anchors.rightMargin: 10
                spacing: 10
                Text{
                    id:movieTimeText
                    anchors.verticalCenter: parent.verticalCenter
                    text:parent.currentTime(player.position)+"/"+parent.currentTime(player.duration)
                    color: "white"
                }
                //调节音量
                Slider{
                    id:voice
                    width: myPlayer.width*0.2
                    height: 10
                    value:0.5
                    stepSize: 0.01
                    maximumValue: 1
                    minimumValue: 0
                    anchors.verticalCenter: parent.verticalCenter
                    style: SliderStyle {
                        groove: Rectangle {
                            implicitWidth: myPlayer.width*0.2
                            implicitHeight: 8
                            color: "white"
                            radius: 2
                            Rectangle {
                                anchors.left: parent.left
                                anchors.top: parent.top
                                anchors.bottom: parent.bottom
                                width: player.volume>0?parent.width*player.volume:0
                                color: "blue"
                            }
                        }
                        handle: Rectangle {
                            anchors.centerIn: parent
                            color: control.pressed ? "white" : "darkgray"
                            border.color: "gray"
                            border.width: 2
                            implicitWidth: 15
                            implicitHeight: 15
                            radius:7.5
                            Rectangle{
                                width: parent.width-8
                                height: width
                                radius: width/2
                                color: "blue"
                                anchors.centerIn: parent
                            }
                        }
                    }
                }
                //时间格式化
                function currentTime(time)
                {
                    var sec= Math.floor(time/1000);
                    var hours=Math.floor(sec/3600);
                    var minutes=Math.floor((sec-hours*3600)/60);
                    var seconds=sec-hours*3600-minutes*60;
                    var hh,mm,ss;
                    if(hours.toString().length<2)
                        hh="0"+hours.toString();
                    else
                        hh=hours.toString();
                    if(minutes.toString().length<2)
                        mm="0"+minutes.toString();
                    else
                        mm=minutes.toString();
                    if(seconds.toString().length<2)
                        ss="0"+seconds.toString();
                    else
                        ss=seconds.toString();
                    return hh+":"+mm+":"+ss
                }
            }
        }
    }
}


Mask.qml

import QtQuick 2.0
import QtQuick.Window 2.1

/**灯箱效果,禁止操作下层的对象*/
Rectangle {
    //anchors.fill: getRoot(this)
    id:maskRect_1
    color: 'lightgrey'
    opacity: 0.5
    z:99
    MouseArea{
        anchors.fill: parent;
        onPressed:{
             mouse.accepted = true
        }
    }

    function getRoot(item)
    {
        return (item.parent !== null) ? getRoot(item.parent) : item;
    }


    Component.onCompleted: {
        this.parent = getRoot(this);
        this.anchors.fill = parent;
        console.log("Mask z:" + z )
    }
}

ConfigWindow.qml

import QtQuick 2.0
import QtQuick.Controls 1.1
import QtQuick.Window 2.1
import QtQuick.Controls.Styles 1.1


Window {
    id: root
    color: 'lightblue'

    // 公有属性
    property bool showMask : false;
    property string animationType : 'none';
    property int duration : 500
    property int easingType : Easing.OutBounce


    // 私有属性
    property int innerX;
    property int innerY;
    property int innerWidth;
    property int innerHeight;
    property double innerOpacity;


    //------------------------------
    // 事件
    //------------------------------
    // 属性备份一下,避免动画对属性进行变更
    Component.onCompleted: {
        console.log("save config window data")
        save();
    }

    function show()
    {
        console.log("now show popup");
        console.log("show cfgWin x:" + root.x + " y:" + root.y)
        reset();
        switch (animationType)
        {
            case "fade":     animFadeIn.start(); break;
            case "width":    animWidthIncrease.start(); break;
            case "height":   animHeightIncrease.start(); break;
            case "size":     animBig.start(); break;
            case "flyDown":  animInDown.start(); break;
            case "flyUp":    animInUp.start(); break;
            case "flyLeft":  animInLeft.start(); break;
            case "flyRight": animInRight.start(); break;
            default:         this.visible = true;
        }
    }

    function hide()
    {
        switch (animationType)
        {
            case "fade":    connector.target = animFadeOut;        animFadeOut.start(); break;
            case "width":   connector.target = animWidthDecrease;  animWidthDecrease.start();   break;
            case "height":  connector.target = animHeightDecrease; animHeightDecrease.start();  break;
            case "size":    connector.target = animSmall;          animSmall.start();   break;
            case "flyDown": connector.target = animOutUp;          animOutUp.start();   break;
            case "flyUp":   connector.target = animOutDown;        animOutDown.start(); break;
            case "flyLeft": connector.target = animOutRight;       animOutRight.start();break;
            case "flyRight":connector.target = animOutLeft;        animOutLeft.start(); break;
            default:        close();
        }
    }

    // 动画结束后调用的脚本
    Connections{
        id: connector
        target: animInDown
        onStopped: close()
    }



    //------------------------------
    // 辅助方法
    //------------------------------
//    function getRoot(item)
//    {
//        return (item.parent !== null) ? getRoot(item.parent) : item;
//    }

    function save()
    {
        console.log("save cfgWin x:" + root.x + " y:" + root.y)
        //innerX = root.x;
        //innerY = root.y;
        innerWidth = root.width;
        innerHeight = root.height;
        innerOpacity = root.opacity;
        console.log("save x=" + innerX + " y="+innerY + " w=" + innerWidth + " h="+innerHeight);
    }

    function reset()
    {
        //root.x = innerX;
        //root.y = innerY;
        root.width = innerWidth
        root.height = innerHeight;
        root.opacity = innerOpacity;
        //root.scale = 1;

        connector.target = null;
        //mask.visible = showMask;
        root.visible = true;
    }

    // 立即关闭
    function close()
    {
        mainWinMask.visible = false;
        root.visible = false;
        log();
    }

    function log()
    {
        console.log("x=" + x + " y="+y + " w=" + width + " h="+height);
    }




    //------------------------------
    // 遮罩
    //------------------------------
//    // 禁止事件穿透
//    MouseArea{
//        anchors.fill: parent;
//        onPressed:{
//             mouse.accepted = true
//        }
//        //drag.target: root  // root可拖动
//    }

    // 灯箱遮罩层
//    Mask{
//        id: mask
//        visible: false
//    }





    //------------------------------
    // 动画
    //------------------------------
    // fadeIn/fadeOut
    PropertyAnimation {
        id:animFadeIn
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'opacity';
        from: 0;
        to: root.innerOpacity
    }
    PropertyAnimation {
        id: animFadeOut
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'opacity';
        from: root.innerOpacity;
        to: 0
    }

    // width
    PropertyAnimation {
        id: animWidthIncrease
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'width';
        from: 0;
        to: root.innerWidth
    }
    PropertyAnimation {
        id: animWidthDecrease
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'width';
        from: root.innerWidth;
        to: 0
    }

    // height
    PropertyAnimation {
        id: animHeightIncrease
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'height';
        from: 0;
        to: root.innerHeight
    }
    PropertyAnimation {
        id: animHeightDecrease
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'height';
        from: root.innerHeight;
        to: 0
    }


    // size(如何控制size动画的中心点)
    PropertyAnimation {
        id: animBig
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'scale';
        from: 0;
        to: 1
    }
    PropertyAnimation {
        id: animSmall
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'scale';
        from: 1;
        to: 0
    }

    // fly in
    PropertyAnimation {
        id: animInRight
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'x';
        from: -root.innerWidth;
        to: root.innerX
    }
    PropertyAnimation {
        id: animInLeft
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'x';
        from: root.width;
        to: root.innerX
    }
    PropertyAnimation {
        id: animInUp
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'y';
        from: root.height;
        to: root.innerY
    }
    PropertyAnimation {
        id: animInDown
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'y';
        from: -root.innerHeight
        to: root.innerY
    }


    // fly out
    PropertyAnimation {
        id: animOutRight
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'x';
        from: root.innerX;
        to: root.width
    }
    PropertyAnimation {
        id: animOutLeft
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'x';
        from: root.innerX;
        to: -root.width
    }
    PropertyAnimation {
        id: animOutUp
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'y';
        from: root.innerY;
        to: -root.height
    }
    PropertyAnimation {
        id: animOutDown
        target: root
        duration: root.duration
        easing.type: root.easingType
        property: 'y';
        from: root.innerY
        to: root.height
    }

    flags: Qt.Window | Qt.FramelessWindowHint
    //标题栏区域
    Rectangle {
            id: cfgWindowTitle                       //创建标题栏
            anchors.top: parent.top             //对标题栏定位
            anchors.left: parent.left
            anchors.right: parent.right
            height: 25                          //设置标题栏高度
            color: "#7B7B7B"                    //设置标题栏背景颜色

            MouseArea { //为窗口添加鼠标事件
                anchors.fill: parent
                acceptedButtons: Qt.LeftButton //只处理鼠标左键
                property point clickPos: "0,0"
                onPressed: { //接收鼠标按下事件
                        clickPos  = Qt.point(mouse.x,mouse.y)
                        mouse.accepted = true
                }
                onPositionChanged: { //鼠标按下后改变位置
                        //鼠标偏移量
                        var delta = Qt.point(mouse.x-clickPos.x, mouse.y-clickPos.y)

                        //如果窗体继承自QWidget,用setPos
                        root.setX(root.x+delta.x)
                        root.setY(root.y+delta.y)
                }
            }
            //窗口标题
            Text{
                anchors.left: parent.left
                anchors.leftMargin: 10
                anchors.top:parent.top
                anchors.topMargin: 6
                text: "Config"
                color:"white"
                font.bold: true
                font.pixelSize: 15
            }
            Row{
                anchors.right: parent.right
                anchors.top:parent.top
                width:60
                height: 25

                //退出
                Button{
                    id:quitButton
                    width: 60
                    height: 25
                    style:ButtonStyle{
                        background: Rectangle{
                            border.width: control.hovered ? 2 : 1
                            border.color: (control.hovered || control.pressed) ? "red" : "#7B7B7B"
                            color: (control.hovered || control.pressed) ? "red" : "#7B7B7B"
                            Text {
                                id: quitButtonTxt
                                text: qsTr("Quit")
                                anchors.centerIn: parent
                                color: "black"
                            }
                        }
                    }
                    onClicked: {hide();}
                }
            }
    }
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>


int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));


    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}






猜你喜欢

转载自blog.csdn.net/jianyuling199/article/details/78637900