Despite trying many ideas unsuccessfully to fix the problem raised in this thread Can I do more than MediaView.dispose to make MediaView ready again?, I thought I'd try the jVLC library released by @moster67 in the hope that its video player would be more resilient than the JavaFX one.
I thought the swap would be relatively simple, with my existing use of the MediaView event _Complete being replaced by the jVLC _Finished event, however I've encountered a fatal error in the handling of _Finished.
The code which determines each video file name in sequence, calls the player sub and waits for the video to complete/finish before proceeding to the next is:
This is the video player code called from the sequencer:
And here is the code for handling the jVLC _Finished event and the watchdog timer (Timer5) in case a video stalls so that the PlayerIntros sub resumes:
Now when Timer5_tick occurs, the PlayerIntros sub resumes and the video player moves onto the next video file as expected/intended, however if the video finishes before Timer5_tick occurs and the vpp_Finished event occurs as indicated by the "VLC video finished" entry in the log, the application crashes after a couple of seconds with no further error entry in the log.
If I change that _Finished event handler to:
ie. change CallSubDelayed to CallSub, then after the "VLC video finished" entry, there is an error entry in the log before the application crashes:
I'm out of my depth here, but is it to do with VLC and the main application not being on the same thread? If so, how should I be handling the event to achieve my objective? Or if I'm on the wrong tack, then any suggestions/guidance would be appreciated.
I thought the swap would be relatively simple, with my existing use of the MediaView event _Complete being replaced by the jVLC _Finished event, however I've encountered a fatal error in the handling of _Finished.
The code which determines each video file name in sequence, calls the player sub and waits for the video to complete/finish before proceeding to the next is:
B4X:
Private vlc As B4JVlcj
vlc.Initialize("vpp")
Dim jo As JavaObject = Display.GridPane1
jo.RunMethodJo("getChildren", Null).RunMethod("add", Array(vlc.player))
Sub PlayerIntros
' Show videos or images of players in that preferential order
For Each k As Object In mapHomeTeam.Keys
Dim playerId As String = mapHomeTeam.Get(k)
For a = 0 To mapHomeTeam.Size - 1
If playerId = arrayHome(a, 0) Then
playerNumberForDisplay = arrayHome(a, 1).SubString2(0, 2).Trim
mediaFile = arrayHome(a, 1).SubString(arrayHome(a, 1).IndexOf(" ") + 2)
If IntroVideos(mediaFile & ".mp4", playerNumberForDisplay) = True Then
Timer5.Enabled = True
wait For video_Complete
Display.GridPane1.Visible = False
Display.playernumber.As(B4XView).SendToBack
Timer5.Enabled = False
Else if IntroImages(mediaFile & ".png", playerNumberForDisplay) = True Then
Sleep(txtIntrosDelay.text * 1000)
Display.ivGraphics.visible = False
Else
IntroImages("noImageFile.png", playerNumberForDisplay)
Sleep(txtIntrosDelay.text * 1000)
Display.ivGraphics.visible = False
End If
End If
Next
Next
Display.playerNumber.Visible = False
End Sub
This is the video player code called from the sequencer:
B4X:
Sub IntroVideos(videoFile As String, pNum As String ) As Boolean 'Player - first initial dot space secondname dot MP4, player singlet number / Coaches & Team Managers - firstname dot space secondname dot MP4, ""
Display.playerNumber.text = pNum
Display.playerNumber.visible = True
If File.Exists(PublicApplicationDataFolder & "\Intros\" & cmbOrganisationID.Value, videoFile) = True Then
vlc.Play(PublicApplicationDataFolder & "\Intros\" & cmbOrganisationID.Value & "\" & videoFile)
Log(PublicApplicationDataFolder & "\Intros\" & cmbOrganisationID.Value & "\" & videoFile)
Display.GridPane1.Visible = True
vlc.mute
Display.playernumber.As(B4XView).BringToFront
Return True
Else
Display.playernumber.As(B4XView).BringToFront
Return False
End If
End Sub
And here is the code for handling the jVLC _Finished event and the watchdog timer (Timer5) in case a video stalls so that the PlayerIntros sub resumes:
B4X:
Sub vpp_Finished
Log("VLC video finished")
CallSubDelayed(Me, "video_Complete")
End Sub
Sub Timer5_tick
Log("Video watchdog timer error for file: " & mediaFile & ".mp4")
CallSubDelayed(Me, "video_Complete")
End Sub
Now when Timer5_tick occurs, the PlayerIntros sub resumes and the video player moves onto the next video file as expected/intended, however if the video finishes before Timer5_tick occurs and the vpp_Finished event occurs as indicated by the "VLC video finished" entry in the log, the application crashes after a couple of seconds with no further error entry in the log.
If I change that _Finished event handler to:
B4X:
Sub vpp_Finished
Log("VLC video finished")
CallSub(Me, "video_Complete")
End Sub
ie. change CallSubDelayed to CallSub, then after the "VLC video finished" entry, there is an error entry in the log before the application crashes:
C:\Users\Public\Documents\BDSConsulting\BDSScoreboard\Intros\NBL1\K. Vitale.mp4
Video watchdog timer error for file: K. Vitale.mp4
C:\Users\Public\Documents\BDSConsulting\BDSScoreboard\Intros\NBL1\A. Bandilovska.mp4
VLC video finished
main._vpp_finished (java line: 9584)
java.lang.RuntimeException: java.lang.RuntimeException: java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-3-thread-1
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:523)
at anywheresoftware.b4a.keywords.Common.CallSubNew(Common.java:461)
at b4j.example.main._vpp_finished(main.java:9584)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:577)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:111)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:98)
at com.tillekesoft.vlcj.B4JVlcj$1.finished(B4JVlcj.java:351)
at uk.co.caprica.vlcj.player.events.MediaPlayerEndReachedEvent.notify(MediaPlayerEndReachedEvent.java:41)
at uk.co.caprica.vlcj.player.DefaultMediaPlayer$NotifyEventListenersRunnable.run(DefaultMediaPlayer.java:2253)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.RuntimeException: java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-3-thread-1
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:140)
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:514)
... 14 more
Caused by: java.lang.IllegalStateException: Not on FX application thread; currentThread = pool-3-thread-1
at javafx.graphics@18.0.1/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:297)
at javafx.graphics@18.0.1/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:458)
at javafx.graphics@18.0.1/javafx.scene.Parent$3.onProposedChange(Parent.java:474)
at javafx.base@18.0.1/com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:328)
at javafx.base@18.0.1/com.sun.javafx.collections.VetoableListDecorator.remove(VetoableListDecorator.java:220)
at javafx.graphics@18.0.1/javafx.scene.Parent.toBack(Parent.java:749)
at javafx.graphics@18.0.1/javafx.scene.Node.toBack(Node.java:2004)
at anywheresoftware.b4a.objects.B4XViewWrapper.SendToBack(B4XViewWrapper.java:728)
at b4j.example.main$ResumableSub_PlayerIntros.resume(main.java:6435)
at anywheresoftware.b4a.BA.checkAndRunWaitForEvent(BA.java:156)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:105)
... 15 more
I'm out of my depth here, but is it to do with VLC and the main application not being on the same thread? If so, how should I be handling the event to achieve my objective? Or if I'm on the wrong tack, then any suggestions/guidance would be appreciated.