forked from qmlbook/qt6book
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
5 changed files
with
381 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,321 @@ | ||
# Playing Media | ||
|
||
The most basic case of multimedia integration in a QML application is for it to playback media. This can be achieved using the `MediaPlayer` element. This QML component has a `source` property pointing at the media to play. When a media source has been bound, it is simply a matter of calling the `play` function to start playing it. | ||
The most basic case of multimedia integration in a QML application is for it to playback media. The `QtMultimedia` module supports this by providing a dedicated QML component: the `MediaPlayer`. | ||
|
||
If you want to play visual media such as pictures or videos, you must also set up a `VideoOutput` element to place the resulting image in the user interface. The `MediaPlayer` running the playback is bound to the video output through the `source` property. | ||
The `MediaPlayer` component is a non-visual item that connects a media source to one or several output channel(s). Depending on the nature of the media (i.e. audio, image or video) various output channel(s) can be configured. | ||
|
||
In the example shown below, the `MediaPlayer` is given a file with video contents as `source`. A `VideoOutput` is created and bound to the media player. As soon as the main component has been fully initialized, i.e. at `Component.onCompleted`, the player’s `play` function is called. | ||
## Playing audio | ||
|
||
In the following example, the `MediaPlayer` plays a mp3 sample audio file from a remote URL in an empty window: | ||
|
||
```qml | ||
import QtQuick 6.2 | ||
import QtQuick | ||
import QtMultimedia | ||
Item { | ||
Window { | ||
width: 1024 | ||
height: 600 | ||
height: 768 | ||
visible: true | ||
MediaPlayer { | ||
id: player | ||
source: "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_2MG.mp3" | ||
audioOutput: AudioOutput {} | ||
} | ||
Component.onCompleted: { | ||
player.play() | ||
} | ||
} | ||
``` | ||
|
||
In this example, the `MediaPlayer` defines two attributes: | ||
|
||
- `source`: it contains the URL of the media to play. It can either be embedded (`qrc://`), local (`file://`) or remote (`https://`). | ||
- `audioOutput`: it contains an audio output channel, `AudioOutput`, connected to a physical output device. By default, it will use the default audio output device of the system. | ||
|
||
As soon as the main component has been fully initialized, the player’s `play` function is called: | ||
|
||
```qml | ||
Component.onCompleted: { | ||
player.play() | ||
} | ||
``` | ||
|
||
## Playing a video | ||
|
||
If you want to play visual media such as pictures or videos, you must also define a `VideoOutput` element to place the resulting image or video in the user interface. | ||
|
||
In the following example, the `MediaPlayer` plays a mp4 sample video file from a remote URL and centers the video content in the window: | ||
|
||
```qml | ||
import QtQuick | ||
import QtMultimedia | ||
Window { | ||
width: 1920 | ||
height: 1080 | ||
visible: true | ||
MediaPlayer { | ||
id: player | ||
source: "trailer_400p.ogg" | ||
source: "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1920_18MG.mp4" | ||
audioOutput: AudioOutput {} | ||
videoOutput: videoOutput | ||
} | ||
VideoOutput { | ||
id: videoOutput | ||
anchors.fill: parent | ||
source: player | ||
anchors.margins: 20 | ||
} | ||
Component.onCompleted: { | ||
player.play(); | ||
player.play() | ||
} | ||
} | ||
``` | ||
|
||
Basic operations such as altering the volume when playing media are controlled through the `volume` property of the `MediaPlayer` element. There are other useful properties as well. For instance, the `duration` and `position` properties can be used to build a progress bar. If the `seekable` property is `true`, it is even possible to update the `position` when the progress bar is tapped. However, the `position` property is read-only, instead we must use the `seek` method. The example below shows how this is added to the basic playback example above. | ||
In this example, the `MediaPlayer` defines a third attribute: | ||
|
||
```qml | ||
Rectangle { | ||
id: progressBar | ||
- `videoOutput`: it contains the video output channel, `VideoOutput`, representing the visual space reserved to display the video in the user interface. | ||
|
||
anchors.left: parent.left | ||
anchors.right: parent.right | ||
anchors.bottom: parent.bottom | ||
anchors.margins: 100 | ||
::: warning | ||
Please note that the `VideoOutput` component is a visual item. As such, it's essential that it is created within the visual components hierarchy and not within the `MediaPlayer` itself. | ||
::: | ||
|
||
height: 30 | ||
|
||
color: "lightGray" | ||
## Controlling the playback | ||
|
||
Rectangle { | ||
anchors.left: parent.left | ||
anchors.top: parent.top | ||
anchors.bottom: parent.bottom | ||
The `MediaPlayer` component offers several useful properties. For instance, the `duration` and `position` properties can be used to build a progress bar. If the `seekable` property is `true`, it is even possible to update the `position` when the progress bar is tapped. | ||
|
||
It's also possible to leverage `AudioOutput` and `VideoOutput` properties to customize the experience and provide, for instance, volume control. | ||
|
||
The following example adds custom controls for the playback: | ||
|
||
width: player.duration>0?parent.width*player.position/player.duration:0 | ||
* a volume slider | ||
* a play/pause button | ||
* a progress slider | ||
|
||
```qml | ||
import QtQuick | ||
import QtQuick.Controls | ||
import QtMultimedia | ||
color: "darkGray" | ||
Window { | ||
id: root | ||
width: 960 | ||
height: 400 | ||
visible: true | ||
MediaPlayer { | ||
id: player | ||
source: "file:///path-to-your-video-file.mp4" | ||
audioOutput: audioOutput | ||
videoOutput: videoOutput | ||
} | ||
MouseArea { | ||
anchors.fill: parent | ||
AudioOutput { | ||
id: audioOutput | ||
volume: volumeSlider.value | ||
} | ||
VideoOutput { | ||
id: videoOutput | ||
width: videoOutput.sourceRect.width | ||
height: videoOutput.sourceRect.height | ||
anchors.horizontalCenter: parent.horizontalCenter | ||
} | ||
Slider { | ||
id: volumeSlider | ||
anchors.top: parent.top | ||
anchors.right: parent.right | ||
anchors.margins: 20 | ||
orientation: Qt.Vertical | ||
value: 0.5 | ||
} | ||
onClicked: { | ||
if (player.seekable) { | ||
player.seek(player.duration * mouse.x/width); | ||
Item { | ||
height: 50 | ||
anchors.left: parent.left | ||
anchors.right: parent.right | ||
anchors.bottom: parent.bottom | ||
anchors.margins: 20 | ||
Button { | ||
anchors.horizontalCenter: parent.horizontalCenter | ||
text: player.playbackState === MediaPlayer.PlayingState ? qsTr("Pause") : qsTr("Play") | ||
onClicked: { | ||
switch(player.playbackState) { | ||
case MediaPlayer.PlayingState: player.pause(); break; | ||
case MediaPlayer.PausedState: player.play(); break; | ||
case MediaPlayer.StoppedState: player.play(); break; | ||
} | ||
} | ||
} | ||
Slider { | ||
id: progressSlider | ||
width: parent.width | ||
anchors.bottom: parent.bottom | ||
enabled: player.seekable | ||
value: player.duration > 0 ? player.position / player.duration : 0 | ||
background: Rectangle { | ||
implicitHeight: 8 | ||
color: "white" | ||
radius: 3 | ||
Rectangle { | ||
width: progressSlider.visualPosition * parent.width | ||
height: parent.height | ||
color: "#1D8BF8" | ||
radius: 3 | ||
} | ||
} | ||
handle: Item {} | ||
onMoved: function () { | ||
player.position = player.duration * progressSlider.position | ||
} | ||
} | ||
} | ||
Component.onCompleted: { | ||
player.play() | ||
} | ||
} | ||
``` | ||
|
||
The `position` property is only updated once per second in the default case. This means that the progress bar will update in large steps unless the duration of the media is long enough, compared to the number of pixels that the progress bar is wide. This can, however, be changed through accessing the `mediaObject` property and its `notifyInterval` property. It can be set to the number of milliseconds between each position update, increasing the smoothness of the user interface. | ||
### The volume slider | ||
A vertical `Slider` component is added on the top right corner of the window, allowing the user to control the volume of the media: | ||
|
||
```qml | ||
Connections { | ||
target: player | ||
onMediaObjectChanged: { | ||
if (player.mediaObject) { | ||
player.mediaObject.notifyInterval = 50; | ||
} | ||
} | ||
Slider { | ||
id: volumeSlider | ||
anchors.top: parent.top | ||
anchors.right: parent.right | ||
anchors.margins: 20 | ||
orientation: Qt.Vertical | ||
value: 0.5 | ||
} | ||
``` | ||
|
||
When using `MediaPlayer` to build a media player, it is good to monitor the `status` property of the player. It is an enumeration of the possible statuses, ranging from `MediaPlayer.Buffered` to `MediaPlayer.InvalidMedia`. The possible values are summarized in the bullets below: | ||
The volume attribute of the `AudioOutput` is then mapped to the value of the slider: | ||
|
||
```qml | ||
AudioOutput { | ||
id: audioOutput | ||
volume: volumeSlider.value | ||
} | ||
``` | ||
|
||
* `MediaPlayer.UnknownStatus`. The status is unknown. | ||
### Play / Pause | ||
|
||
A `Button` component reflects the playback state of the media and allows the user to control this state: | ||
|
||
* `MediaPlayer.NoMedia`. The player has no media source assigned. Playback is stopped. | ||
```qml | ||
Button { | ||
anchors.horizontalCenter: parent.horizontalCenter | ||
text: player.playbackState === MediaPlayer.PlayingState ? qsTr("Pause") : qsTr("Play") | ||
onClicked: { | ||
switch(player.playbackState) { | ||
case MediaPlayer.PlayingState: player.pause(); break; | ||
case MediaPlayer.PausedState: player.play(); break; | ||
case MediaPlayer.StoppedState: player.play(); break; | ||
} | ||
} | ||
} | ||
``` | ||
|
||
Depending on the playback state, a different text will be displayed in the button. When clicked, the corresponding action will be triggered and will either play or pause the media. | ||
|
||
* `MediaPlayer.Loading`. The player is loading the media. | ||
::: tip | ||
The possible playback states are listed below: | ||
* `MediaPlayer.PlayingState`: The media is currently playing. | ||
* `MediaPlayer.PausedState`: Playback of the media has been suspended. | ||
* `MediaPlayer.StoppedState`: Playback of the media is yet to begin. | ||
::: | ||
|
||
|
||
* `MediaPlayer.Loaded`. The media has been loaded. Playback is stopped. | ||
### Interactive progress slider | ||
|
||
A `Slider` component is added to reflect the current progress of the playback. It also allows the user to control the current position of the playback. | ||
|
||
* `MediaPlayer.Stalled`. The loading of media has stalled. | ||
```qml | ||
Slider { | ||
id: progressSlider | ||
width: parent.width | ||
anchors.bottom: parent.bottom | ||
enabled: player.seekable | ||
value: player.duration > 0 ? player.position / player.duration : 0 | ||
background: Rectangle { | ||
implicitHeight: 8 | ||
color: "white" | ||
radius: 3 | ||
Rectangle { | ||
width: progressSlider.visualPosition * parent.width | ||
height: parent.height | ||
color: "#1D8BF8" | ||
radius: 3 | ||
} | ||
} | ||
handle: Item {} | ||
onMoved: function () { | ||
player.position = player.duration * progressSlider.position | ||
} | ||
} | ||
``` | ||
|
||
This slider will only be enabled when the media is `seekable`: | ||
|
||
* `MediaPlayer.Buffering`. The media is being buffered. | ||
```qml | ||
Slider { | ||
/* ... */ | ||
enabled: player.seekable | ||
/* ... */ | ||
} | ||
``` | ||
|
||
Its value will be set to the current media progress, i.e. `player.position / player.duration`: | ||
|
||
* `MediaPlayer.Buffered`. The media has been buffered, this means that the player can start playing the media. | ||
```qml | ||
Slider { | ||
/* ... */ | ||
value: player.duration > 0 ? player.position / player.duration : 0 | ||
/* ... */ | ||
} | ||
``` | ||
|
||
When the slider is moved by the user, the media position will be updated: | ||
```qml | ||
Slider { | ||
/* ... */ | ||
onMoved: function () { | ||
player.position = player.duration * progressSlider.position | ||
} | ||
/* ... */ | ||
} | ||
``` | ||
|
||
* `MediaPlayer.EndOfMedia`. The end of the media has been reached. Playback is stopped. | ||
## The media status | ||
|
||
When using `MediaPlayer` to build a media player, it is good to monitor the `status` property of the player. Here is an enumeration of the possible statuses, ranging from `MediaPlayer.Buffered` to `MediaPlayer.InvalidMedia`. The possible values are summarized in the bullets below: | ||
|
||
* `MediaPlayer.NoMedia`. No media has been set. Playback is stopped. | ||
* `MediaPlayer.Loading`. The media is currently being loaded. | ||
* `MediaPlayer.Loaded`. The media has been loaded. Playback is stopped. | ||
* `MediaPlayer.Buffering`. The media is buffering data. | ||
* `MediaPlayer.Stalled`. The playback has been interrupted while the media is buffering data. | ||
* `MediaPlayer.Buffered`. The media has been buffered, this means that the player can start playing the media. | ||
* `MediaPlayer.EndOfMedia`. The end of the media has been reached. Playback is stopped. | ||
* `MediaPlayer.InvalidMedia`. The media cannot be played. Playback is stopped. | ||
* `MediaPlayer.UnknownStatus`. The status of the media is unknown. | ||
|
||
As mentioned in the bullets above, the playback state can vary over time. Calling `play`, `pause` or `stop` alters the state, but the media in question can also have an effect. For example, the end can be reached, or it can be invalid, causing playback to stop. The current playback state can be tracked through the `playbackState` property. The values can be `MediaPlayer.PlayingState`, `MediaPlayer.PausedState` or `MediaPlayer.StoppedState`. | ||
As mentioned in the bullets above, the playback state can vary over time. Calling `play`, `pause` or `stop` alters the state, but the media in question can also have an effect. For example, the end can be reached, or it can be invalid, causing playback to stop. | ||
|
||
::: danger | ||
To be checked with TQC | ||
|
||
Using the `autoPlay` property, the `MediaPlayer` can be made to attempt go to the playing state as soon as the `source` property is changed. A similar property is the `autoLoad` causing the player to try to load the media as soon as the `source` property is changed. The latter property is enabled by default. | ||
|
||
It is also possible to let the `MediaPlayer` to loop a media item. The `loops` property controls how many times the `source` is to be played. Setting the property to `MediaPlayer.Infinite` causes endless looping. Great for continuous animations or a looping background song. | ||
|
||
::: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import QtQuick | ||
import QtMultimedia | ||
|
||
Window { | ||
width: 1024 | ||
height: 768 | ||
visible: true | ||
|
||
MediaPlayer { | ||
id: player | ||
source: "https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_2MG.mp3" | ||
audioOutput: AudioOutput {} | ||
} | ||
|
||
Component.onCompleted: { | ||
player.play() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import QtQuick | ||
import QtQuick.Controls | ||
import QtMultimedia | ||
|
||
Window { | ||
id: root | ||
width: 960 | ||
height: 400 | ||
visible: true | ||
|
||
MediaPlayer { | ||
id: player | ||
source: "file:///path-to-your-video-file.mp4" | ||
audioOutput: audioOutput | ||
videoOutput: videoOutput | ||
} | ||
|
||
AudioOutput { | ||
id: audioOutput | ||
volume: volumeSlider.value | ||
} | ||
|
||
VideoOutput { | ||
id: videoOutput | ||
width: videoOutput.sourceRect.width | ||
height: videoOutput.sourceRect.height | ||
anchors.horizontalCenter: parent.horizontalCenter | ||
} | ||
|
||
Slider { | ||
id: volumeSlider | ||
anchors.top: parent.top | ||
anchors.right: parent.right | ||
anchors.margins: 20 | ||
orientation: Qt.Vertical | ||
value: 0.5 | ||
} | ||
|
||
Item { | ||
height: 50 | ||
anchors.left: parent.left | ||
anchors.right: parent.right | ||
anchors.bottom: parent.bottom | ||
anchors.margins: 20 | ||
|
||
Button { | ||
anchors.horizontalCenter: parent.horizontalCenter | ||
text: player.playbackState === MediaPlayer.PlayingState ? qsTr("Pause") : qsTr("Play") | ||
onClicked: { | ||
switch(player.playbackState) { | ||
case MediaPlayer.PlayingState: player.pause(); break; | ||
case MediaPlayer.PausedState: player.play(); break; | ||
case MediaPlayer.StoppedState: player.play(); break; | ||
} | ||
} | ||
} | ||
|
||
Slider { | ||
id: progressSlider | ||
width: parent.width | ||
anchors.bottom: parent.bottom | ||
enabled: player.seekable | ||
value: player.duration > 0 ? player.position / player.duration : 0 | ||
background: Rectangle { | ||
implicitHeight: 8 | ||
color: "white" | ||
radius: 3 | ||
Rectangle { | ||
width: progressSlider.visualPosition * parent.width | ||
height: parent.height | ||
color: "#1D8BF8" | ||
radius: 3 | ||
} | ||
} | ||
handle: Item {} | ||
onMoved: function () { | ||
player.position = player.duration * progressSlider.position | ||
} | ||
} | ||
} | ||
|
||
Component.onCompleted: { | ||
player.play() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import QtQuick | ||
import QtMultimedia | ||
|
||
Window { | ||
width: 1920 | ||
height: 1080 | ||
visible: true | ||
|
||
MediaPlayer { | ||
id: player | ||
source: "https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_1920_18MG.mp4" | ||
audioOutput: AudioOutput {} | ||
videoOutput: videoOutput | ||
} | ||
|
||
VideoOutput { | ||
id: videoOutput | ||
anchors.fill: parent | ||
anchors.margins: 20 | ||
} | ||
|
||
Component.onCompleted: { | ||
player.play() | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.