最近这段时间在学习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(); }