QML video seek() function jumping back a few seconds after seeking - qt

Whenever I try to use the seek() function it jumps to the new position and then back to a random point. The desired behaviour would be to simply jump to the new position. I have the following video qml type.
Video {
id: video
focus: true
MouseArea {
anchors.fill: parent
onClicked: {
video.playbackState === MediaPlayer.PlayingState ? video.pause() : video.play()
playButton.visible = false
}
}
}
Whenever I try to skip ahead by a certain amount (for example 1 second) by doing the following, the video does this and then jumps back roughly 0.5s.
RoundButton{
Layout.fillWidth: true
text: ">"
onClicked: {
video.seek(video.position + 1000)
}
}
When Paused the Video jumps to the correct location. As soon as the video is played again jumps back to a same seemingly random point which is roughly 0.5s earlier.
If a new position is beeing seeked while the video is playing this seems to happen instantly. So the video jumps to the correct location and then jumps back.
When playing the video for the first time i get the following error a bunch of times.
W MapperHal: buffer descriptor with invalid usage bits 0x2000
When I stop the video and start playing it again I get the following error once.
W MediaPlayerNative: info/warning (3, 0)
The goal is simply to be able to skip through the video in predefined timesteps.

Related

WebEngineView + virtual keyboard - maintain scroll position after resizing (focused input)

I have a very simple browser app based on WebEngineView and virtual keyboard made in Qt Quick.
Everything works fine - the keyboard is shown perfectly each time I click on an input in the webview, but what bothers me is that if I click on an input that is at the bottom, the keyboard covers it after opening and I cannot see what I'm typing.
I tried solving it by resizing the WebEngineView element to accomodate for the keyboard height, like most mobile apps work. It works, I can scroll the page under the keyboard but the keyboard still covers the input and I need to scroll manually.
Is there any way I could adjust the web view scroll position so the keyboard doesn't cover the focused input from QML?
I cannot do it at a single website because I allow navigation to any website user wants, so I need some universal method.
Here is my qml code:
import QtQuick 2.12
import QtQuick.Window 2.12
import FreeVirtualKeyboard 1.0
import QtWebEngine 1.8
Window {
id: appContainer;
visible: true
width: 1280
height: 600
title: qsTr("WebEngineView")
property string pathUrl: "https://www.w3schools.com/html/html_forms.asp"
WebEngineView {
id: webview
width: appContainer.width
url: appContainer.pathUrl
height: appContainer.height
}
/*
Virtual keyboard
*/
InputPanel {
id: inputPanel
z: 99
y: appContainer.height
anchors.left: parent.left
anchors.right: parent.right
states: State {
name: "visible"
when: Qt.inputMethod.visible
PropertyChanges {
target: inputPanel
y: appContainer.height - inputPanel.height
}
}
transitions: Transition {
from: ""
to: "visible"
reversible: true
ParallelAnimation {
NumberAnimation {
properties: "y"
duration: 150
easing.type: Easing.InOutQuad
}
}
onRunningChanged: {
if(!running && inputPanel.state == "visible") {
// finished showing keyboard
webview.height = appContainer.height - inputPanel.height
console.log('Keyboard shown')
} else if(running && inputPanel.state != "visible") {
// begins to hide keyboard
webview.height = appContainer.height
console.log('Keyboard starts to hide');
}
}
}
}
}
So far the resizing part works okay - I do it in onRunningChanged so the webview resizes before the transition starts and after it ends - this prevents ugly empty space showing during transition.
Update
I have achieved the effect I wanted using webview.runJavaScript together with scrollIntoView after showing the keyboard:
webview.runJavaScript("document.activeElement.scrollIntoView({block: 'nearest', inline: 'nearest', behavior: 'smooth'})");
However I'm not sure if this is solution is the best, as I don't like the fact of involving javascript evaluation into the process. I'd like to know if there's any more "native" way of doing this.
Resize WebEngineView, scroll into view
The problem with resizing the WebEngineView is that HTML will see that your device screen suddenly shrunk and may decide to present a vastly different layout, for example move menu from top to side of the screen.
Even if this has not happened, layout has changed. The position on the new "screen" does not correspond to the position on the old one, there is no 1:1 relation, which is why it scrolls to a seemingly random spot in the first place.
We can tell webpage to scroll a focused element into view of new viewport:
If it was already onscreen than nothing happens.
If not, webpage scrolls so that the element fits on the screen if possible. scrollIntoView has parameters to scroll to the top/bottom of the screen as desired
So when onscreen keyboard is opened:
Save original scrollPosition
Resize WebEngineView
Optionally assign scrollPosition to saved value - although it probably won't do you any good
Use runJavaScript to determine activeElement and make it scrollIntoView
Repeat same steps when onscreen keyboard is dismissed.
Do not resize, scroll manually
Another approach would be to not resize the "screen" and just scroll the element into view if it got covered.
This would require Qt to change VisualViewport while leaving LayoutViewport intact (see this and this for more information) but it seems that Qt cannot do that, at least not through QML alone.
That leaves us with having to do it manually: determine position with getBoundingClientRect, calculate how much space does keyboard cover, and if it is not inside our calculated uncovered view area - scrollTo it.
(you will still need runJavaScript to get inside the webpage)
Perhaps this and this SO questions can help
Other options
#Hazelnutek reported some success with document.activeElement.scrollIntoViewIfNeeded()
Please see discussion in comments to this answer below:

QMl QtControls.Slider returns value with decimal points

I am using a Slider with below properties set for it -
Item {
id: root
signal sliderMoved
property alias value: control.value
QtControls.Slider {
orientation: Qt.Vertical
snapMode: QtControls.Slider.SnapOnRelease
from: 59.0
to: 86.0
stepSize : 1.0
onMoved: root.sliderMoved()
}
}
From user QML where this slider is used thru loader
Connections {
target: sliderLoader.item
onSliderMoved: {
console.warn(sliderLoader.item.value);
}
}
But eachtime when the slider is dragged, it returns the value with decimal points. I am expecting value like 59,60,61, 62.....till 86 not like 59.12, 63.45 etc. How to get rid of these decimal points from the returned slider value when the slider is moved
The reason you are seeing decimals is because your slider snapMode is set to snapOnRelease. This means the slider value will not honor your step size until the user releases the slider. Changing to snapAlways will fix this, but it will cause the slider to appear "choppy" during use. If you would still like to use snapOnRelease; I see two options for you moving forward.
Instead of using onMoved to emit sliderMoved, you may use.
onPressedChanged: {
if (!pressed) {
root.sliderMoved()
}
}
This way the signal will only be emitted once the slider has been released/snapped, and you will log the whole number value the user has decided upon.
If it is necessary to log every change the user makes as you have it written currently, round up or down as desired in your log statement to prevent decimals in the logs.

Qt Location Editing a MapPolyLine

My objective is to allow a user to edit a MapPolyLine using the mouse to drag and drop points on the line. I have implemented this by creating a C++ model that exposes a path() method for dumping all points to draw the line.
I then used this model to display a set of MapCircles for each vertex on the line. These Mapcircles have drag events which update the MapPolyLine model after drag has finished.
This works a treat for lines with < 500 vertices. Then performance suffers really badly. Some of my lines (from GPX files) have 10,000 vertices.
So I tried to only expose MapCircles on the line close to the mouse hover point. But (unless I am mistaken) the MouseArea for a MapPolyLine appears to be the bounding box of the line and hover can be triggered in strange situations.
I found that the onEntered event worked well enough for me to work out the position on the line and then display a set of MapCircles close to that position. It is a sub-optimal solution though as the user has to actively click the line to get these MapCircles to show.
My question is: "am I using the right strategy to allow a MapPolyLine to be edited?". I cannot see any other way of doing it in the current implementation of QtLocation.
The code is now quite complex. It's structure is:-
GPXModel {
id: gpxModel
}
MapPolyLine {
path: gpxModel.path
MouseArea {
onEntered: {
var mapCoord = gpxLine.mapToItem(mapView,mouseX,mouseY);
var coord = mapView.toCoordinate(Qt.point(mapCoord.x,mapCoord.y));
//tell the model where we are in the array of points so we can display MapCircles around this point
gpxModel.setEditLocationFromCoordinate(coord);
}
}
}
MapItemView {
model: gpxModel
MapCircle {
//this is a custom role I use to return a sub-set of points set by setEditLocationFromCoordinate
center: positionRole
MouseArea{
anchors.fill: parent
drag.target: parent
onDragActiveChanged: {
if(!drag.active){
gpxModel.updatePositionOfMarker();
}
}
}
}
}
The project is open sourced at https://github.com/citizenfish/servalan_2020 with the caveat that I am using this as a learning exercise so it's all a bit hacked together.
EDIT: I do not want to use webview based solutions. The point of the project was to do this in native Qt with minimal reliance on third party javascript libraries/browsers.

Unable to loop video using QML MediaPlayer

I am trying to create a simple video player that just plays a specified video on loop. While the video plays as expected, it does not loop.
The following is the code I am using:
import QtQuick 2.0
import QtMultimedia 5.0
Rectangle
{
width : 320
height : 240
signal buttonPressed(string msg)
property string currentVideo
function playVideo(videoName)
{
currentVideo = videoName
videoPlayer.source = videoName
videoPlayer.seek(1)
videoPlayer.play()
}
function loopVideo()
{
if(videoPlayer.duration === 0)
{
playVideo(currentVideo)
}
}
function stopVideoPlayback()
{
videoPlayer.stop()
}
MediaPlayer {
id: videoPlayer
source: ""
autoPlay: false
autoLoad: false
loops: 100
}
VideoOutput {
id: videoOutput
source: videoPlayer
anchors.fill: parent
visible: true
}
}
I call playVideo from C++. It starts playing as expected. However, once it completes, the frame freezes on the last one. I tried looping it by calling the loopVideo function in a QTimer. That does not work either.
What might I be doing wrong?
Your code is ok. (slight tip: you might want to use MediaPlayer.Infinite instead 100 for looping)
I believe that your situation is same like mine.
I have played a little with MediaPlayer component and at my end I am unable to seek video because seekable is always false. And seekable is false because somehow QML uses my file as live stream, and that results duration property to be 0.
Also note that onPaused and onStopped are never triggered and position is just increasing after end of video (live stream never ends).
Now I think that this is related to looping, because basically looping seeks back to 0. Because there is no duration (MediaPlayer thinks it is live stream) it cannot seek (and loop).
One nasty workaround that I found is this (append to your code):
Rectangle {
id: root
//...
MediaPlayer {
//...
onPositionChanged: {
if (position > VIDEO_LENGTH) {
root.stopVideoPlayback()
root.playVideo(root.currentVideo)
}
}
}
}
Where VIDEO_LENGTH is length of your video file in milliseconds.
Click here for MediaPlayer element documentation
UPDATE:
It looks like that is bug in Qt for mingw version (closed bug report).
UPDATE 2: I have downloaded MSVC version of Qt and media player works as it is supposed.
So it is bug in Qt for mingw.
Either use this workaround (which I wouldn't recommend) or download MSVC version.
I have created new bug report here.
Using stopped signal try this code :
MediaPlayer {
id: mediaplayer
source: "groovy_video.mp4"
onStopped: play()
}

Qml timer not triggered in the right interval

I've created a timer QML app, and I'm using Timer qml component.
The interval is set to 1000 milliseconds (the default one)... but it seems to be working properly only when the app is with focus on it.
When I put it in background it seems that it's not triggered every time, and because of that I got some mistakes in the app.
I've tried to find anything related to that in the documentation, but I couldn't
The timer code is really simple:
Timer {
id: timer
repeat: true
onTriggered: {msRemaining -= 1000; Core.secondsToText(type);}
}
Anyone has any idea about that and how to fix it?
Versions:
Qt 5.2
QML 2.0
OS X 10.9
The QML Timer element is synchronized with the animation timer. Since the animation timer is usually set to 60fps, the resolution of Timer will be at best 16ms. You should also note that in Qt Quick 2 the animation timer is synced to the screen refresh (while in Qt Quick 1 it is hard-coded to 16ms). So when your app runs in background i think the refreshing is stopped and consequently your timer which is synced to screen refresh will stop working properly.
If you want to show elapsed time using a timer as you did is not a good idea because it is not precise. You can use javascript Date() function like:
import QtQuick 2.0
Item {
id: root
width: 200; height: 230
property double startTime: 0
property int secondsElapsed: 0
function restartCounter() {
root.startTime = 0;
}
function timeChanged() {
if(root.startTime==0)
{
root.startTime = new Date().getTime(); //returns the number of milliseconds since the epoch (1970-01-01T00:00:00Z);
}
var currentTime = new Date().getTime();
root.secondsElapsed = (currentTime-startTime)/1000;
}
Timer {
id: elapsedTimer
interval: 1000;
running: true;
repeat: true;
onTriggered: root.timeChanged()
}
Text {
id: counterText
text: root.secondsElapsed
}
}
I have a QML app with a Timer object, running on Android:
With Qt 4.8, the Timer works fine when the QML app is in the background.
With Qt 5.4, the Timer no longer works when the QML app is in the background. For example, the QML app can no longer receive onTriggered() signal. When the QML app is brought back to the foreground again, the Timer starts working again. It appears that the Qt signals are blocked while the QML app is in the background.
So this looks like a regression in Qt. And the best solution would be to wait until this regression is fixed.
This the code snippet working for me, It should inside ApplicationWindow{} or Item{} elements.
Timer{
id: closeTimer
interval: 10000
repeat: true
running: true
onTriggered: {
drawer.close()
}
}
Both repeat and running should be true.

Resources