Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Zoom Player command sending & data retrieving via TCP


  • Please log in to reply
No replies to this topic
mprost
  • Members
  • 5 posts
  • Last active: Jun 02 2014 03:25 PM
  • Joined: 15 Dec 2006
Hi.

Here's my adaptation of Zed Gecko's excellent client script for TCP/IP network communication, to be able to send commands to and receive data from Zoom Player. This is just an example, no practical use intended, but I thought it would be interesting to share, as it can be used as a starting point for showing info in an external display (LCD), making an external interface, even sending commands across the Internet!

Here's the code:

;adapted from Zed Gecko's excellent client script for TCP/IP network communication http://www.autohotkey.com/forum/viewtopic.php?t=13829
;this script allows to establish a connection with zoom player and then shows all messages received from it. Additionally you can send a specific message every second, as well as a function (command)

;notes about Zoom Player TCP/IP interface (more info http://forum.inmatrix.com/index.php?showtopic=7051)
;----------------------------------------

;The TCP/IP interface is the most flexible and powerful of the control interfaces. All you need to do is open a TCP/IP connection to the machine's IP and Port (if you're using the control program on the same computer as Zoom Player, the IP is always 127.0.0.1 (localhost)). The default port is "4769", but it can be changed through the options dialog or by using a command line ("/TCP:4768" for example). This is useful if you need to control multiple instances of the player, each with its own TCP/IP control.

;Once a TCP connection is made, Zoom Player will send initial information such as the current version number and if a media file is loaded, information about what's currently playing. This will be sent in a format like this (please note the information is sent in serveral lines, ending with an empty one):
;0000 Zoom Player Home MAX
;0001 6.00
;1300 1
;1000 2
;1800 filename
;

;During reproduction several messages are automatically sent (i.e., there's no need to send anything to receive this), like play/pause status changes and the info displayed in the control bar (each second).

;The TCP interface is very simple to use. All information passed along the TCP interface is comprised of text lines starting with a 4 digit code, followed by a data value (only if the command requires additional parameters passed). For example, sending "1120" (with no additional parameters) will return a text value of "1120 15000" which means that the video position is currently 15 seconds.

;All text data (sent and received) is UTF8 encoded to preserve international language data. Furthermore, make sure to terminate each command with CRLF (Ascii #13#10), otherwise Zoom Player will not be able to detect when once commands ends and a second begins.

;more info about funcions: http://www.inmatrix.com/zplayer/highlights/zpfunctions.shtml

; CONFIGURATION SECTION:

; Specify address and port of the server.
Network_Address = 127.0.0.1
Network_Port = 4769
#SingleInstance force

; ----------------------------
; END OF CONFIGURATION SECTION
; ----------------------------

Gui, Add, Edit, x10 y40 w50 vSendText, 1120
Gui, Add, Button, x70 y39 gSendviaNet, Send info request every second, response ->
Gui, Add, Text, x305 y44 w700 vMonitoredOutput

Gui, Add, Edit, x10 y71 w50 vSendFunction, fnPause
Gui, Add, Button, x70 y70 gSendviaNetFunction, Send function

Gui, Add, Button, x10 y10 gConnection_Init, Connect

gui, add, edit, vEditControl x10 y110 W1000 r60 HwndEditHwnd
Gui, Show

return

Connection_Init:
	OnExit, ExitSub  ; For connection cleanup purposes.
	
	; Connect to any type of server:
	socket := ConnectToAddress(Network_Address, Network_Port)
	if socket = -1  ; Connection failed (it already displayed the reason).
	    ExitApp
	
	; Find this script's main window:
	Process, Exist  ; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts).
	DetectHiddenWindows On
	ScriptMainWindowId := WinExist("ahk_class AutoHotkey ahk_pid " . ErrorLevel)
	DetectHiddenWindows Off
	
	; When the OS notifies the script that there is incoming data waiting to be received,
	; the following causes a function to be launched to read the data:
	NotificationMsg = 0x5555  ; An arbitrary message number, but should be greater than 0x1000.
	OnMessage(NotificationMsg, "ReceiveData")
	
	; Set up the connection to notify this script via message whenever new data has arrived.
	; This avoids the need to poll the connection and thus cuts down on resource usage.
	FD_READ = 1     ; Received when data is available to be read.
	FD_CLOSE = 32   ; Received when connection has been closed.
	if DllCall("Ws2_32\WSAAsyncSelect", "UInt", socket, "UInt", ScriptMainWindowId, "UInt", NotificationMsg, "Int", FD_READ|FD_CLOSE)
	{
	    MsgBox % "WSAAsyncSelect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
	    ExitApp
	}
return

SendviaNet:
	Gui, Submit, NoHide
	TextToSend=%SendText%`r`n ;zoom player requires all commands to end with CRLF (Ascii #13#10)
	;timer for sending of messages
	settimer MessageSending, 1000
;	SentText =
return

SendviaNetFunction:
	Gui, Submit, NoHide
	FunctionToSend=5100 %SendFunction%`r`n
	SendData(socket,FunctionToSend)
return

MessageSending:
;	traytip %A_now%,sending #%TextToSend%#
	SendData(socket,TextToSend)
return

SendData(wParam,SendData)
{
	socket := wParam
	sendret := DllCall("Ws2_32\send", "UInt", socket, "Str", SendData, "Int", strlen(SendData), "Int", 0)
;	traytip %A_now%,socket #%socket%# senddata #%senddata%# sendret #%sendret%#
}

ConnectToAddress(IPAddress, Port)
; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success.
{
    VarSetCapacity(wsaData, 32)  ; The struct is only about 14 in size, so 32 is conservative.
    result := DllCall("Ws2_32\WSAStartup", "UShort", 0x0002, "UInt", &wsaData) ; Request Winsock 2.0 (0x0002)
    ; Since WSAStartup() will likely be the first Winsock function called by this script,
    ; check ErrorLevel to see if the OS has Winsock 2.0 available:
    if ErrorLevel
    {
        MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
        return -1
    }
    if result  ; Non-zero, which means it failed (most Winsock functions return 0 upon success).
    {
        MsgBox % "WSAStartup() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    AF_INET = 2
    SOCK_STREAM = 1
    IPPROTO_TCP = 6
    socket := DllCall("Ws2_32\socket", "Int", AF_INET, "Int", SOCK_STREAM, "Int", IPPROTO_TCP)
    if socket = -1
    {
        MsgBox % "socket() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError")
        return -1
    }

    ; Prepare for connection:
    SizeOfSocketAddress = 16
    VarSetCapacity(SocketAddress, SizeOfSocketAddress)
    InsertInteger(2, SocketAddress, 0, AF_INET)   ; sin_family
    InsertInteger(DllCall("Ws2_32\htons", "UShort", Port), SocketAddress, 2, 2)   ; sin_port
    InsertInteger(DllCall("Ws2_32\inet_addr", "Str", IPAddress), SocketAddress, 4, 4)   ; sin_addr.s_addr

    ; Attempt connection:
    if DllCall("Ws2_32\connect", "UInt", socket, "UInt", &SocketAddress, "Int", SizeOfSocketAddress)
    {
        MsgBox % "connect() indicated Winsock error " . DllCall("Ws2_32\WSAGetLastError") . "?"
        return -1
    }
    return socket  ; Indicate success by returning a valid socket ID rather than -1.
}


ReceiveData(wParam, lParam)
; By means of OnMessage(), this function has been set up to be called automatically whenever new data
; arrives on the connection. 
{
    global ShowReceived
    global EditHwnd
    socket := wParam
    ReceivedDataSize = 4096  ; Large in case a lot of data gets buffered due to delay in processing previous data.
    Loop  ; This loop solves the issue of the notification message being discarded due to thread-already-running.
    {
        VarSetCapacity(ReceivedData, ReceivedDataSize, 0)  ; 0 for last param terminates string for use with recv().
        ReceivedDataLength := DllCall("Ws2_32\recv", "UInt", socket, "Str", ReceivedData, "Int", ReceivedDataSize, "Int", 0)
;       traytip %a_now%,received: %ReceivedData%
        if ReceivedDataLength = 0  ; The connection was gracefully closed,
            ExitApp  ; The OnExit routine will call WSACleanup() for us.
        if ReceivedDataLength = -1
        {
            WinsockError := DllCall("Ws2_32\WSAGetLastError")
            if WinsockError = 10035  ; WSAEWOULDBLOCK, which means "no more data to be read".
                return 1
            if WinsockError <> 10054 ; WSAECONNRESET, which happens when Network closes via system shutdown/logoff.
                ; Since it's an unexpected error, report it.  Also exit to avoid infinite loop.
                MsgBox % "recv() indicated Winsock error " . WinsockError
            ExitApp  ; The OnExit routine will call WSACleanup() for us.
        }
        ; Otherwise, process the data received.
		ReceivedDataANSI:=ConvertUTF8toANSI(ReceivedData) ;we translate from unicode to ANSI, all text data received from zoom player is UTF8 encoded
        
        ;for debugging reasons, we dump everything we receive
        GuiControlGet EditControl
        GuiControl text,EditControl,%EditControl%%ReceivedDataANSI%
        ;WM_VSCROLL (0x115), SB_BOTTOM (7)
        SendMessage, 0x115, 0x0000007, 0, , ahk_id %EditHwnd% ;to force scroll to bottom

        ;now we listen for the monitored message
        Loop, parse, ReceivedData, `n, `r
        {
        	GuiControlGet SendText
            if (substr(A_LoopField,1,4)=SendText) {
	           	guicontrol text, MonitoredOutput,%A_LoopField%
            }
;           Tooltip % ShowReceived
        }
    }
    return 1  ; Tell the program that no further processing of this message is needed.
}

InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity.  To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
    Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
        DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}

ConvertUTF8toANSI(textToConvert) ;Code by PhiLho (http://www.autohotkey.com/forum/viewtopic.php?t=11167&highlight=utf8)
{
  textLength := StrLen(textToConvert)
  VarSetCapacity(uniText, textLength * 2, 0) ; Worse case (all Ascii)
  r := DllCall("MultiByteToWideChar" ;UTF-8 to Unicode UTF-16 
        , "UInt", 65001        ; CodePage: CP_ACP=0 (current Ansi), CP_UTF7=65000, CP_UTF8=65001
        , "UInt", 0            ; dwFlags
        , "Str", textToConvert ; LPSTR lpMultiByteStr
        , "Int", textLength    ; cbMultiByte: -1=null terminated
        , "UInt", &uniText     ; LPCWSTR lpWideCharStr
        , "Int", textLength)   ; cchWideChar: 0 to get required size
  VarSetCapacity(ansiText, textLength, 0)
  r := DllCall("WideCharToMultiByte" ;Unicode UTF-16 to ANSI
        , "UInt", 0            ; CodePage: CP_ACP=0 (current Ansi), CP_UTF7=65000, CP_UTF8=65001
        , "UInt", 0            ; dwFlags
        , "Str", uniText       ; LPCWSTR lpWideCharStr
        , "Int", textLength    ; cchWideChar: size in WCHAR values, -1=null terminated
        , "Str", ansiText      ; LPSTR lpMultiByteStr
        , "Int", textLength    ; cbMultiByte: 0 to get required size
        , "UInt", 0            ; LPCSTR lpDefaultChar
        , "UInt", 0)           ; LPBOOL lpUsedDefaultChar
  return ansiText
}

GuiClose:
ExitSub:  ; This subroutine is called automatically when the script exits for any reason.
; MSDN: "Any sockets open when WSACleanup is called are reset and automatically
; deallocated as if closesocket was called."
DllCall("Ws2_32\WSACleanup")
ExitApp

Cheers,

mprost