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