Jump to content

Recommended Posts

Posted

Hello,

I'm lurking on this forum for quite some time and it helped me a lot. But this time I couldn't find anything for my problem and neither could I on google.

I am using autoit for a couple of years, but nothing serious, just some casual automation, so I would still consider myself a beginner.

This time I tried to do a bigger "project" and butchered together some things i found on here and wrote some myself. 

My program consists of 2 scripts, a server and a client. The Server just handles the server stuff (duh) and some small calculations. 

The Client connects to a media player (currently only vlc) and communicates with it (only play/pause and current time of the video).

It's nothing fancy, GUI and comments are still all over the place and more of a proof of concept.

But now I hit a wall and I can't figure it out myself.

When I connect the client to the server and try to send some data from the media player I get the error:

""C:\Users\chinx\Documents\Autoit\VideoSyncer\start vlc with webinterface.au3" (78) : ==> Array variable has incorrect number of subscripts or subscript dimension range exceeded.:
Global $GlobalPlaystate = $playandtimeA[1]
Global $GlobalPlaystate = ^ ERROR" 

On the Server I get the same in line 88 "$Timestamp = $PlayPauseAndTimestamp[1]"

I tried to increase the Array size before using it.

I get a console output with:

"0
:0"

and expected is "0:0"

So I thought it could be a white space or a @CRLF causing the problem so I stripped it with StringStripWS. But I still get the same output.

I tested some other things I can't remember and I just don't know how to proceed anymore, so I'm asking for help if anyone knows what the problem is.

 

I hope the formatting is correct and I hope I don't have anything personal in the scripts, but here they are:

Server:

#cs ----------------------------------------------------------------------------

AutoIt Version: 3.3.8.1 (Or greater)
Author:      Ken Piper

Script Function:
    Template multi-client server base code.
    Use as a base for making an efficient server program.

    This base will just accept connections and echo back what it receives,
        and kill the connection if it is dead or inactive for x seconds.
    It will not do any other work, that must be added seperately!

#ce ----------------------------------------------------------------------------
#include <Array.au3>


TCPStartup()
Opt("TCPTimeout", 0)

#region ;Safe-to-edit things are below
Global $BindIP = "0.0.0.0"    ;Listen on all addresses
Global $BindPort = 8080        ;Listen on port 8080
Global $Timeout = 15000        ;Max idle time is 15 seconds before calling a connection "dead"
Global $PacketSize = 2048    ;Max packet size per-check is 2KB
Global $MaxClients = 50        ;Max simultaneous clients is 50
Global $RangeTimestamp = 1000   ;Allowed Range of Timestamp +/-
#endregion ;Stuff you shouldn't touch is below

Global $Listen
Global $Clients[1][6] ;[Index][Socket, IP, Timestamp, Buffer]
Global $Ws2_32 = DllOpen("Ws2_32.dll") ;Open Ws2_32.dll, it might get used a lot
Global $NTDLL = DllOpen("ntdll.dll") ;Open ntdll.dll, it WILL get used a lot
Global $CleanupTimer = TimerInit() ;This is used to time when things should be cleaned up

OnAutoItExitRegister("Close") ;Register this function to be called if the server needs to exit

$Clients[0][0] = 0
$Listen = TCPListen($BindIP, $BindPort, $MaxClients) ;Start listening on the given IP/port
If @error Then Exit 1 ;Exit with return code 1 if something was already bound to that IP and port

While 1
    USleep(1000, $NTDLL) ;This is needed because TCPTimeout is disabled. Without this it will run one core at ~100%.
    ;The USleep function takes MICROseconds, not milliseconds, so 1000 = 1ms delay.
    ;When working with this granularity, you have to take in to account the time it takes to complete USleep().
    ;1000us (1ms) is about as fast as this should be set. If you need more performance, set this from 5000 to 1000,
    ;but doing so will make it consume a bit more CPU time to get that extra bit of performance.
    Check() ;Check recv buffers and do things
    If TimerDiff($CleanupTimer) > 1000 Then ;If it has been more than 1000ms since Cleanup() was last called, call it now
        $CleanupTimer = TimerInit() ;Reset $CleanupTimer, so it is ready to be called again
        Cleanup() ;Clean up the dead connections
    EndIf
    Local $iSock = TCPAccept($Listen) ;See if anything wants to connect
    If $iSock = -1 Then ContinueLoop ;If nothing wants to connect, restart at the top of the loop
    Local $iSize = UBound($Clients, 1) ;Something wants to connect, so get the number of people currently connected here
    If $iSize - 1 > $MaxClients And $MaxClients > 0 Then ;If $MaxClients is greater than 0 (meaning if there is a max connection limit) then check if that has been reached
        TCPCloseSocket($iSock) ;It has been reached, close the new connection and continue back at the top of the loop
        ContinueLoop
    EndIf
    ReDim $Clients[$iSize + 1][6] ;There is room for a new connection, allocate space for it here
    $Clients[0][0] = $iSize ;Update the number of connected clients
    $Clients[$iSize][0] = $iSock ;Set the socket ID of the connection
    $Clients[$iSize][1] = SocketToIP($iSock, $Ws2_32) ;Set the IP Address the connection is from
    $Clients[$iSize][2] = TimerInit() ;Set the timestamp for the last known activity timer
    $Clients[$iSize][3] = "" ;Blank the recv buffer
    $Clients[$iSize][4] = "" ;Blank Play/Pause
    $Clients[$iSize][5] = "" ;Blank Timestamp
WEnd

Func Check() ;Function for processing
    If $Clients[0][0] < 1 Then Return ;If there are no clients connected, stop the function right now
    For $i = 1 To $Clients[0][0] ;Loop through all connected clients
        $sRecv = TCPRecv($Clients[$i][0], $PacketSize) ;Read $PacketSize bytes from the current client's buffer
        If $sRecv <> "" Then $Clients[$i][3] &= $sRecv ;If there was more data sent from the client, add it to the buffer
        If $Clients[$i][3] = "" Then ContinueLoop ;If the buffer is empty, stop right here and check more clients
        $Clients[$i][2] = TimerInit() ;If it got this far, there is data to be parsed, so update the activity timer
        #region ;Example packet processing stuff here. This is handling for a simple "echo" server with per-packet handling
            $sRecv = StringLeft($Clients[$i][3], StringInStr($Clients[$i][3], @CRLF, 0, -1)) ;Pull all data to the left of the last @CRLF in the buffer
            ;This does NOT pull the first complete packet, this pulls ALL complete packets, leaving only potentially incomplete packets in the buffer
            If $sRecv = "" Then ContinueLoop ;Check if there were any complete "packets"
            $Clients[$i][3] = StringTrimLeft($Clients[$i][3], StringLen($sRecv) + 1) ;remove what was just read from the client's buffer
            $sPacket = StringSplit($sRecv, @CRLF, 1) ;Split all complete packets up in to an array, so it is easy to work with them
            For $j = 1 To $sPacket[0] ;Loop through each complete packet; This is where any packet processing should be done
                $PlayPauseAndTimestamp = StringSplit ($sPacket[$j],":",2)
                $PlayPause = $PlayPauseAndTimestamp[0]
                $Timestamp = $PlayPauseAndTimestamp[1]
            Next
            ;Update Status of [4]&[5]
            $Clients[$i][4] = $PlayPause
            $Clients[$i][5] = $Timestamp
            ;Compare Data with other Clients
            for $k = 1 to $Clients[0][0]
                If $Clients[$i][4] <> $Clients[$k][4] Then
                    $discreptPP = 1
                EndIf
                If $Clients[$k][5] > $Clients[$i][5]+$RangeTimestamp AND $Clients[$k][5] < $Clients[$i][5]-$RangeTimestamp Then
                    $discreptTimestamp = 1
                EndIf
            Next
            If $discreptPP Or $discreptTimestamp Then
                For $l = 1 to $Clients[0][0]
                    TCPSend ($Clients[$l][0], $Clients[$i][4]&":"&$Clients[$i][5]&@CRLF)
                Next
            EndIf


            ;If needed send the updated data to Client
                ; How to Send Data: TCPSend($Clients[$i][0], "Echoing line: " & $sPacket[$j] & @CRLF) ;Echo back the packet the client sent
        #endregion ;Example
    Next
EndFunc

Func Cleanup() ;Clean up any disconnected clients to regain resources
    If $Clients[0][0] < 1 Then Return ;If no clients are connected then return
    Local $iNewSize = 0
    For $i = 1 To $Clients[0][0] ;Loop through all connected clients
        $Clients[$i][3] &= TCPRecv($Clients[$i][0], $PacketSize) ;Dump any data not-yet-seen in to their recv buffer
        If @error > 0 Or TimerDiff($Clients[$i][2]) > $Timeout Then ;Check to see if the connection has been inactive for a while or if there was an error
            TCPCloseSocket($Clients[$i][0]) ;If yes, close the connection
            $Clients[$i][0] = -1 ;Set the socket ID to an invalid socket
        Else
            $iNewSize += 1
        EndIf
    Next
    If $iNewSize < $Clients[0][0] Then ;If any dead connections were found, drop them from the client array and resize the array
        Local $iSize = UBound($Clients, 2) - 1
        Local $aTemp[$iNewSize + 1][$iSize + 1]
        Local $iCount = 1
        For $i = 1 To $Clients[0][0]
            If $Clients[$i][0] = -1 Then ContinueLoop
            For $j = 0 To $iSize
                $aTemp[$iCount][$j] = $Clients[$i][$j]
            Next
            $iCount += 1
        Next
        $aTemp[0][0] = $iNewSize
        $Clients = $aTemp
    EndIf
EndFunc

Func Close()
    DllClose($Ws2_32) ;Close the open handle to Ws2_32.dll
    DllClose($NTDLL) ;Close the open handle to ntdll.dll
    For $i = 1 To $Clients[0][0] ;Loop through the connected clients
        TCPCloseSocket($Clients[$i][0]) ;Force the client's connection closed
    Next
    TCPShutdown() ;Shut down networking stuff
EndFunc

Func SocketToIP($iSock, $hDLL = "Ws2_32.dll") ;A rewrite of that _SocketToIP function that has been floating around for ages
    Local $structName = DllStructCreate("short;ushort;uint;char[8]")
    Local $sRet = DllCall($hDLL, "int", "getpeername", "int", $iSock, "ptr", DllStructGetPtr($structName), "int*", DllStructGetSize($structName))
    If Not @error Then
        $sRet = DllCall($hDLL, "str", "inet_ntoa", "int", DllStructGetData($structName, 3))
        If Not @error Then Return $sRet[0]
    EndIf
    Return "0.0.0.0" ;Something went wrong, return an invalid IP
EndFunc

Func USleep($iUsec, $hDLL = "ntdll.dll") ;A rewrite of the _HighPrecisionSleep function made by monoceres (Thanks!)
    Local $hStruct = DllStructCreate("int64")
    DllStructSetData($hStruct, 1, -1 * ($iUsec * 10))
    DllCall($hDLL, "dword", "ZwDelayExecution", "int", 0, "ptr", DllStructGetPtr($hStruct))
EndFunc

Func _ShowClientArray()
    _ArrayDisplay($Clients)
EndFunc

 Client:

#cs ----------------------------------------------------------------------------

 AutoIt Version: 3.3.14.5
 Author:         myName

 Script Function:
    Template AutoIt script.

#ce ----------------------------------------------------------------------------

; Script Start - Add your code below here
#include<File.au3>
#include <GUIConstants.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>
#include <StringConstants.au3>
Global $VLCplaying = 0
Global $VLCTimestamp = 0
Global $VLCPID
Global $VLCconnected = 0
Global $Serverconnected = 0
Global $SocketServer = 0
Global $playandtimeA[2]

OnAutoItExitRegister (_exit)

$VLCPID = Run ('"C:\Program Files\VideoLAN\VLC\vlc.exe" --extraintf rc --rc-host localhost:2150')

sleep (2000)

TCPStartUp()

$Form1 = GUICreate("Form1", 379, 108, 192, 124)
$Input1 = GUICtrlCreateInput("127.0.2.1", 16, 72, 257, 21)
$Button1 = GUICtrlCreateButton("Connect", 280, 72, 89, 25)
$Label1 = GUICtrlCreateLabel("Not Connected", 16, 8, 36, 17)
GUISetState(@SW_SHOW)



While 1
    $nMsg = GUIGetMsg()
    Switch $nMsg
        Case $GUI_EVENT_CLOSE
            Exit
        Case $Button1
            _ConnectToServer ()
            _ConnectToPlayer ()

    EndSwitch
    if $VLCconnected AND $Serverconnected Then
        _SendLocalState ()
        _SyncLocalState ()
    EndIf
WEnd




_VLCsendandlisten ("")
_VLCsendandlisten ("")


Func _SendLocalState ()
    $timestamp = _VLCtimestamp ()
    $playing = _VLCplaystate ()
    TCPSend ($SocketServer,$playing & ":" & $timestamp & @CRLF)
    ConsoleWrite ($playing & ":" & $timestamp & @CRLF)
EndFunc

Func _GetGlobalState ()
    $playandtimeA[0] = 0
    $playandtimeA[1] = 0
    $playandtime = TCPRecv ($SocketServer,2048)
    $playandtimeA = StringSplit ($playandtime,":",2)
    Global $GlobalTimestamp = $playandtimeA[0]
    Global $GlobalPlaystate = $playandtimeA[1]
EndFunc

Func _SyncLocalState ()
    _GetGlobalState ()
    if $GlobalPlaystate = 1 Then
        _VLCplay ()
    ElseIf $GlobalPlaystate = 0 Then
        _VLCpause ()
    EndIf
    _VLCsettimestamp ($GlobalTimestamp)
EndFunc



Func _exit ()
    TCPShutdown ()
    ProcessClose ($VLCPID)
EndFunc


Func _ConnectToPlayer ()
    Global $SocketVLC=TCPConnect("127.0.0.1",2150)
    $VLCconnected = 1
    If $SocketVLC=-1 Then
        MsgBox(0,"Error","Failed to connect to VLC over tcp proctol on localhost.Is it running and set to listen for console commands on port 2150?")
        $VLCconnected = 0
    EndIf
EndFunc

Func _connectToServer ()
    Global $SocketServer = TCPConnect (GUICtrlRead ($Input1),8080)
    $Serverconnected = 1
    If $SocketServer=-1 Then
        MsgBox(0,"Error","Failed to connect to the Server, contact your master Chinx7 and giv moni.")
        $Serverconnected = 0
    EndIf
EndFunc



Func _VLCtimestamp ()
    _VLCsendandlisten ("get_time")
EndFunc

Func _VLCsettimestamp ($VLCTimestamp)
    _VLCsendandlisten ("seek " & $VLCTimestamp)
EndFunc


Func _VLCplay ()
    if _VLCplaystate = 0 Then
        _VLCsendandlisten ("play")
    EndIf
EndFunc

Func _VLCpause ()
    if _VLCplaystate = 1 Then
        _VLCsendandlisten ("play")
    EndIf
EndFunc


Func _VLCplaystate ()
    Return _VLCsendandlisten ("is_playing")
EndFunc



func _VLCsendandlisten ($commandsend)
    TCPSend($SocketVLC,$commandsend&@CRLF)
    $listen = TCPRecv($SocketVLC,4096);Check if it gets someting back and shows it in inputbox
Return $listen
EndFunc

 

Posted (edited)

Hi Chinx7
My guess is that, sometimes, your variable $playandtime doesn't contain any separator ":"
Then the Array $playandtimeA is recreated with a single element [0], here :

$playandtimeA = StringSplit ($playandtime,":",2)

That's why you never get an error with $GlobalTimestamp = $playandtimeA[0]
But the error arrives immediately after with $GlobalPlaystate = $playandtimeA[1]

I suggest you add a test to detect that $playandtime contains a ":" each time the function is called, for example :

$playandtime = TCPRecv ($SocketServer,2048)
If StringInStr($playandtime, ":") = 0 Then
    MsgBox(0, "Error", "Separator : not found in string $playandtime")
EndIf

You'll tell us :)

Edited by pixelsearch

"I think you are searching a bug where there is no bug... don't listen to bad advice."

Posted

Ah, yeah that error message gets triggered, I need to sleep now, so i'll test that later and report back.

Thank you though, haven't thought about that.

Posted

Ok, I guess I need to look at some TCP-Tutorials, because I just don't get anything from the server,

Is the way I'm  trying to get information from the server by calling the TCPRecv in  _GetGlobalState even correct or do I have to change that?

But thanks to you I fixed another problem. I have to strip all whitespaces from what I'm sending and receiving, because the data I get is sometimes sprinkled with them.

I'll try to find the problems and report back.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...