I have a bug when using CaptureSession on Qt6.4, if I use it in a dynamic component, when I destroy it, the app freezes and I have to kill it.
This problem does not exist when I compile for Desktop.
I tried on Android 11 & 13, the result is the same.
Also it was perfectly working with Qt5 when only using VideoOuput & Camera.
I wrote a minimal example using a Loader, that creates the freeze. If someone has an idea to fix it :
import QtQuick 6.4
import QtQuick.Window 6.4
import QtQuick.Controls 6.4
import QtMultimedia
ApplicationWindow
{
id: window
visible: true
width: Screen.desktopAvailableWidth
height: Screen.desktopAvailableHeight
Loader {
id: loader
anchors.fill: parent
active: activeBtn.checked
sourceComponent: Component {
Item {
id: item
Camera {
id: camera
active: true
focusMode: Camera.FocusModeAutoNear
}
CaptureSession {
camera: camera
videoOutput: videoOutput
}
VideoOutput {
id: videoOutput
anchors.fill: parent
fillMode: VideoOutput.Stretch
}
}
}
}
Button {
id : activeBtn
text: "Active CAM"
checkable: true
anchors {
right: parent.right
bottom: parent.bottom
}
}
}
The app freezes as soon as I unload the component, so when CaptureSession is destroyed.
I would make sure the Camera is stopped before destroying the Component.
In other words, we need to acknowledge on some backends, the camera may be running in a different thread, and, we must make sure we give it sufficient time to terminate the camera and the corresponding VideoOutput preview before forcibly destroying the QML components.
We can keep your original property binding for starting the camera, i.e.
property bool cameraActive: false
Loader {
id: loader
anchors.fill: parent
active: activeBtn.checked || cameraActive
So that will create the component as soon as the Button is checked, HOWEVER, during the initialization of the Component, we changed cameraActive = true; so that it prevents the unchecking of the same Button to destroy the component.
Component.onCompleted: {
cameraActive = true;
}
We use that Button event to stop the Camera with camera.stop().
Connections {
target: activeBtn
function onCheckedChanged() {
if (!activeBtn.checked && camera.active) {
camera.stop();
}
}
}
As soon as we know the Camera has stopped with camera.active === false, then, we change cameraActive = false and that should resume the destruction of your Component.
Camera {
id: camera
active: true
focusMode: Camera.FocusModeAutoNear
onActiveChanged: cameraActive = active
}
Here's your complete code with all of the above changes. Note that I haven't been able to fully verify the code below, so, please treat the code as an educational guide only:
import QtQuick 6.4
import QtQuick.Window 6.4
import QtQuick.Controls 6.4
import QtMultimedia
ApplicationWindow
{
id: window
visible: true
width: Screen.desktopAvailableWidth
height: Screen.desktopAvailableHeight
property bool cameraActive: false
Loader {
id: loader
anchors.fill: parent
active: activeBtn.checked || cameraActive
sourceComponent: Component {
Item {
id: item
Camera {
id: camera
active: true
focusMode: Camera.FocusModeAutoNear
onActiveChanged: cameraActive = active
}
CaptureSession {
camera: camera
videoOutput: videoOutput
}
VideoOutput {
id: videoOutput
anchors.fill: parent
fillMode: VideoOutput.Stretch
}
Connections {
target: activeBtn
function onCheckedChanged() {
if (!activeBtn.checked && camera.active) {
camera.stop();
}
}
}
Component.onCompleted: {
cameraActive = true;
}
}
}
}
Button {
id : activeBtn
text: "Active CAM"
checkable: true
anchors {
right: parent.right
bottom: parent.bottom
}
}
}
Related
main.qml
import QtQuick 2.14
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
Window {
id: window
property variant name: []
property var dialogObject
Button{
text: "open window1"
onClicked: {
var dialogComponent = Qt.createComponent("qrc:/Window1.qml");
if (dialogComponent.status == Component.Ready) {
dialogObject = dialogComponent.createObject(window);
dialogObject.show()
}
}
}
Button{
text: "add array"
onClicked: {
for (var i=1; i<10000000; i++){
name.push(1000000000)
}
}
}
Button{
text: "remove array"
onClicked: delete name[10000000] //do not release!!!!!!!
}
Button{
text: "remove array2"
onClicked: delete name //do not release!!!!!!!!!!!!!!!!
}
Button{
text: "remove array2"
onClicked: name = null //do not release!!!!!!!!!!!!!!!
}
}
Window1.qml
import QtQuick 2.0
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
Window {
id: window1
property var name: [] //do not release!!!!!!!!!!!!!!!!!!!!
Button{
text: "destroy window1"
onClicked: {
window1.destroy()
}
}
Component.onCompleted: {
for (var i=1; i<10000000; i++){
name.push(1000000000)
}
console.log("window1 Component.onCompleted:")
}
Component.onDestruction: {
console.log("window1 Component.onDestuction:")
}
}
Memory of "property var" is not released in Android when deleting a created Qt.createComponent QML component.
I monitor the allocation and deletion of memory and see that when the application loaded there were the following indicators:
When I had pressed the button "add array" - Unknown memory value changed and became 262780kb:
After I had pressed "remove array" or "remove array2" - nothing changed and Unknown memory the same 262780kb. Memory is not released!
If I pressed the button "open window1" the object of Window1 is created and in this time in object Window1 array "name" is had been assigned and memory parameter(Unknown) increase and become 524984kb:
When I press "destroy window1" button - the window is self destructed by call window1.destroy(), but memory is not changed and remain the same 524984kb. If I press "open window1" button more and more the Unknown memory value is increasing proportionally until the app crashes and is not released!
I know that QML has own JS engine and own garbage collector, but when object window1 is has destructed the memory allocated for property var and all included components must released, but it is not happend. The device on which my app worked has small volume of memory and I have a big problem with that. It turns out that the memory is not freed when the object is destroyed. Is there some way to realese memory under property var? How is it to solve this issue with app(Unknown) memory increasing?
Below is an example of how you may redesign:
instead of calling createComponent() it uses a StackView for dynamically creating and destroying Pages with push/pop. Similar approaches could be done with Repeater/ListModel and an appropriate delegate
there is deliberately no call to delete - we expect the JSEngine to take care of dynamic destruction and/or recycling of our garbaged objects
and, if we must, we can call gc() but, I put it in a Qt.callLater() to ensure that the Page has fully been popped of the stack and had time to destroy
import QtQuick
import QtQuick.Controls
Page {
property int count: 0
StackView {
id: stackView
anchors.fill: parent
initialItem: "MainPage.qml"
onDepthChanged: Qt.callLater(callGC)
}
function callGC() {
gc();
++count;
}
footer: Frame { Text { text: "gc count %1".arg(count) } }
}
// MainPage.qml
import QtQuick
import QtQuick.Controls
Page {
header: Frame { Text { text: "Main.qml" } }
Button {
anchors.centerIn: parent
text: "open window1"
onClicked: {
stackView.push("SecondPage.qml")
}
}
}
// SecondPage.qml
import QtQuick
import QtQuick.Controls
Page {
header: Frame { Text { text: "SecondPage.qml" } }
property var name: [ ]
Button {
anchors.centerIn: parent
text: qsTr("destroy window1")
onClicked: stackView.pop();
}
Component.onCompleted: {
//for (let i=1; i < 1000000; i++) {
for (let i=1; i < 1000; i++) {
name.push();
}
}
}
You can Try it Online!
I jumped to conclusions gc() does not release the memory of the "property var", the only way I found to do this is to redefine the variable "property var" with int type, that is, assign it zero, then call gc() and Unknown memory under this variable will be released
I'm developing a phone application using Qt. I have used Qt C++ classes for my whole app but at some point I had to use Qt QML Types for phone camera function in my application and now I am able to display camera's content on a MediaPlayer. I was wondering if it is possible to aslo save this content on phone storage as a .mp4 file or any other formats?
Here is a piece of the code where I display this content on MediaPlayer:
import QtQuick 2.0
import QtMultimedia 5.0
Item {
id: videoPreview
property alias source : player.source
signal closed
MediaPlayer {
id: player
autoPlay: true
onStatusChanged: {
if (status == MediaPlayer.EndOfMedia)
videoPreview.closed();
}
}
VideoOutput {
source: player
anchors.fill : parent
}
MouseArea {
anchors.fill: parent
onClicked: {
videoPreview.closed();
}
}
}
I would really appreciate any kind of hints or helps since I'm a beginner in QML Types :).
And hereby is my main QML file.
import QtQuick 2.0
import QtMultimedia 5.4
Rectangle {
id : cameraUI
width: 640
height: 480
color: "black"
state: "VideoCapture"
states: [
State {
name: "VideoCapture"
StateChangeScript {
script: {
camera.position = Camera.FrontFace
camera.captureMode = Camera.CaptureVideo
camera.start()
}
}
},
State {
name: "VideoPreview"
StateChangeScript {
script: {
camera.stop()
}
}
}
]
Camera {
id: camera
captureMode: Camera.CaptureVideo
videoRecorder {
resolution: "640x480"
frameRate: 30
}
}
VideoPreview {
id : videoPreview
anchors.fill : parent
onClosed: cameraUI.state = "VideoCapture"
visible: cameraUI.state == "VideoPreview"
focus: visible
//don't load recorded video if preview is invisible
source: visible ? camera.videoRecorder.actualLocation : ""
}
VideoOutput {
id: viewfinder
visible: cameraUI.state == "VideoCapture"
x: 0
y: 0
// width: parent.width
width: parent.width - stillControls.buttonsPanelWidth
height: parent.height
source: camera
autoOrientation: true
}
VideoCaptureControls {
id: videoControls
anchors.fill: parent
camera: camera
visible: cameraUI.state == "VideoCapture"
onPreviewSelected: cameraUI.state = "VideoPreview"
}
}
Something like this should work. For more properties check CameraRecorder documentation.
Camera
{
id: camera
captureMode: Camera.CaptureVideo
videoRecorder.muted: true // set any property of recording.
}
VideoOutput
{
source: camera
width: parent.width*0.8
height: parent.height*0.8
autoOrientation: true
anchors.centerIn: parent
MouseArea
{
property var myInt:0
anchors.fill: parent
onClicked:
{
camera.searchAndLock()
camera.videoRecorder.record()
if( myInt > 1 )
{
camera.videoRecorder.stop()
console.log(camera.videoRecorder.outputLocation)
}
myInt=myInt+1
}
}
}
I want to show a Dialog onCompleted under some condition (handling this condition omitted here - a simple if clause)
main.qml:
import QtQuick 2.2
import QtQuick.Controls 1.0
ApplicationWindow
{
id: appWindow
width: 480
height: 640
visible: true
StackView
{
id: stackView
anchors.fill: parent
// Implements back key navigation
focus: true
initialItem: Item
{
width: parent.width
height: parent.height
Button { onClicked: dialog.open() }
// ...
Component.onCompleted: dialog.open()
}
}
MyDialog {id: dialog }
}
MyDialog.qml
import QtQuick 2.0
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0
Dialog
{
id: root
title: "MyDialog"
standardButtons: Qt.NoButton
ColumnLayout
{
id: column
width: parent ? parent.width : 200
Label { text: "hello world"; Layout.fillWidth: true }
}
}
When I launch my app, the screen is dimmed, and the Dialog's shade appears, as though the dialog had width == 0.
Sometimes (rarely) the dialog shows correctly.
If I comment out the Component.onCompleted line and launch the dialog using the Button, it is displayed correctly.
What am I doing wrong?
I'm using Qt 5.5 for Android
Opening the dialog doesn't work correctly if it's opened before the window has a sane geometry.
A safe option is to use the onWidthChanged signal
main.qml
import QtQuick 2.2
import QtQuick.Controls 1.0
ApplicationWindow
{
id: appWindow
width: 480
height: 640
visible: true
StackView
{
id: stackView
anchors.fill: parent
// Implements back key navigation
focus: true
initialItem: Item
{
property bool firstTime: true
width: parent.width
height: parent.height
// ...
// width may be called multiple times
onWidthChanged: if (firstTime) {firstTime = true; dialog.open()}
}
}
MyDialog {id: dialog }
}
Please, refer to official documentation about Dialog QML Type, there you can find next:
Note: do not attempt to bind the width or height of the dialog to the
width or height of its content, because Dialog already tries to size
itself to the content. If your goal is to change or eliminate the
margins, you must override contentItem. If your goal is simply to show
a window (whether modal or not), and your platform supports it, it is
simpler to use Window instead.
So, in your case, it will be better somehow set width of your inner element implicitly. Another quick solution is to add (for example) Rectangle element with given width as only one inner root element of your Dialog. Rest of elements you can place inside this Rectangle.
I'm posting another answer because it is another approach. I remembered how I implemented it on some old project. AFAIK, For now it is the best way. I would be really happy if anyone gave better solution.
You need to add one intermediary element like Timer. This timer (tm in code below) must have small interval (like 30ms) and need to be started on your desired event -- Component.onCompleted. Put your action in onTriggered of this timer.
Here is code:
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true
Timer {
id: tm
interval: 30
onTriggered: dialog.open()
}
StackView {
id: stackView
anchors.fill: parent
// Implements back key navigation
focus: true
initialItem: Item {
width: parent.width
height: parent.heonTriggeredight
Button { onClicked: dialog.open() }
Component.onCompleted: tm.start()
}
}
MyDialog { id: dialog }
}
I'm using ApplicationWindow and StackView to provide multiple pages to a user.
import QtQuick 2.0
import QtQuick.Controls 1.0
ApplicationWindow
{
menuBar: MenuBar {
Menu {
title: qsTr("Menu")
MenuItem
{
text: qsTr("Enter page")
onTriggered: stackView.push("qrc:/qml/SecondPage.qml")
}
MenuItem
{
text: qsTr("Base screen option")
onTriggered: /**/
}
}
}
StackView
{
id: stackView
anchors.fill: parent
focus: true
initialItem: FirstPage {}
}
}
When SecondPage is pushed to the stackView, the global menu is still displayed. I tried making StackView the top element and pushing ApplicationWindow there, but the StackView component needs a parent.
Is there any way to implement a page-specific menu?
Try and use properties in the ApplicationWindow to specify what text to show. Then these properties can be set in the pages in the stack view.
ApplicationWindow {
id: rootWindow
width: 360
height: 360
visible: true
property string option1Text: "Enter page"
property string option2Text: "Base screen option"
menuBar: MenuBar {
Menu {
title: qsTr("Menu")
MenuItem
{
id: option1TextItem
text: qsTr(option1Text)
onTriggered: stackView.push("qrc:/qml/SecondPage.qml")
}
MenuItem
{
id: option2TextItem
text: qsTr(option2Text)
onTriggered: console.log("Triggered option 2")
}
}
}
StackView
{
id: stackView
anchors.fill: parent
focus: true
initialItem: FirstPage {}
}
}
FirstPage:
Rectangle {
anchors.fill: parent
Component.onCompleted: {
rootWindow.option1Text = "First Page"
rootWindow.option2Text = "First Page Option"
}
}
I'm trying to make some sort of fotboll app for android. The problem i'm facing right now, is that I need to make a button in the corner of the screen, (4 different) which you move the game character with. But I've made 1 button now, for moving up, but it doesnt appear on the screen.
Main file:
import QtQuick 2.0
import QtQuick.Window 2.1
Item {
id:root
width:Screen.width
height:Screen.height-10
focus:true
Keys.onPressed: {
if(event.key===Qt.Key_Up)
{
event.accepted = true;
player.y=(player.y) - 40
}
if(event.Key === Qt.Key_Down){
event.accepted = true;
player.y = (player.y)+ 40
}
if (event.key === Qt.Key_Right)
{ event.accepted=true;
player.x=(player.x)+40
}
if (event.key === Qt.Key_Left)
{event.accepted = true;
player.x=(player.x) -40
}
}
Flickable {
width:Screen.width
height:Screen.height
contentHeight: Screen.height*2
contentWidth:Screen.width
interactive:true
boundsBehavior: Flickable.StopAtBounds
contentY: Math.min(contentHeight-height, Math.max(0, player.y-height/2))
contentX: Math.min(contentWidth-width, Math.max(0, player.x-width/2))
Image{
id: feild
anchors.fill:parent
source:"Namnlös.png"
sourceSize.height:Screen.height*2
sourceSize.width:Screen.width
}
Image {
id: player
source:"asd.png"
x:Screen.width/2
y:Screen.height/2
}
}
}
Button file:
import QtQuick 2.0
import QtQuick.Window 2.1
Rectangle {
id: simplebutton
color: "grey"
width: 100; height: 50
Text {
id: buttonLabel
anchors.centerIn: parent
text: "Up"
}
MouseArea {
id: buttonMouseArea
anchors.fill: parent
onClicked: console.log(buttonLabel.text + "clicked" )
}
}
Rectangle {
id: button
property color buttonColor: "lightblue"
property color onHoverColor: "gold"
property color borderColor: "white"
signal buttonClick()
onButtonClick: {
console.log(buttonLabel.text + " clicked" )
}
MouseArea {
onClicked: buttonClick()
hoverEnabled: true
onEntered: parent.border.color = onHoverColor
onExited: parent.border.color = borderColor
}
color: buttonMouseArea.pressed ? Qt.darker(buttonColor, 1.5) : buttonColor
}
Two things:
1) You need to add your button to your layout! Since there you don't add your button into your main file, it won't appear.
2) You need to split your Button file into two separate qml file, since you can only have one "root" item per file.