File "VLC Playlist Manager.bas"

Path: /VLC Playlist Manager/VLC Playlist Manager.bas
File size: 35.94 KB
MIME-type: text/plain
Charset: utf-8

#COMPILE EXE
#DIM ALL
'------------------------------------------------------------------------------
$VER = "1.3"
$SRC = "http://mougino.free.fr/code/VLC Playlist Manager/VLC Playlist Manager.bas"
'------------------------------------------------------------------------------
#INCLUDE ONCE "win32api.inc"
#INCLUDE ONCE "shfolder.inc"
#INCLUDE ONCE "commctrl.inc"
#INCLUDE ONCE "inc/registry.inc"
#INCLUDE ONCE "inc/savepos.inc"
#INCLUDE ONCE "inc/rdir.inc"
#INCLUDE ONCE "inc/exe2unix.inc"
#INCLUDE ONCE "inc/mymsgbox.inc"
#INCLUDE ONCE "inc/selfupdate.inc"
'------------------------------------------------------------------------------
#RESOURCE ".\res\vpm.pbr"
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
%WM_LOADPL      = %WM_USER + 1000
%WM_STARTPL     = %WM_USER + 1001
%WM_RESUMEPL    = %WM_USER + 1002
%WM_UPDATEMF    = %WM_USER + 1003
'------------------------------------------------------------------------------
$VIDEXT         = ",.3gp,.amv,.asf,.avi,.divx,.flv,.m2ts,.m2v,.m4v,.mkv,.mov,.mp4,.mpeg,.mpg,.ogg,.ogv,.qt,.rm,.rmvb,.swf,.vob,.webm,.wmv,"
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
' Changelog
'------------------------------------------------------------------------------
' v1.3
' [X] Fix for Wine 11.0 breaking 'start.exe' retrocompatibility
' v1.2
' [X] Add a program updater!
' [X] Fix 'recent' combobox name when switching to another playlist
' [X] Improve readability of episode names in listview
' v1.1
' [X] Now supports running in Linux via wine!
'   [X] Propose to install and make a Linux App shortcut at startup
' [X] Hidden feature: double-click the eye to manually kill VLC Listener
' [X] Fix long paths in media listview
' [X] Fix new playlist name not appearing in 'recent' combobox
' [X] Force vlc to start playlists with "--no-random" option
' v1.0
' [X] Now supports drag & drop of folders to create a resumable playlist!
'   [X] Update documentation with drag & drop of folders
' [X] Fix VLC Listener to detect VLC instances in languages other than english
' [X] Handle vlc 64-bit installation folder
' v0.3
' [X] Fix badly handled UTF-8 characters
' [X] Fix long paths in recent playlist combobox
' v0.2
' [X] Add eye icon to show activity of VLC Listener
' [X] Add (!) icon to alert last VLC media couldn't be found
' [X] CenterMsgBox()
' [X] Fix relative paths broken when opened from local playlist
' [X] Fix xspf support
' [X] Fix recent playlist combobox duplicates
' [X] Fix empty recent playlists combobox when opening an invalid playlist
' v0.1
' [X] Double-click on a media file resumes the playlist at this point
' [X] Button "Start playlist" selects first media then start playlist
' [X] Button "Resume playlist" selects last media and resumes playlist
' [X] Make dialog able to receive files by drag & drop
'   [X] Start/resume drag and dropped playlist
' [X] Save last played file from playlist
' [X] Buttons:
'   [X] Start/Resume playlist
'   [X] Help
' [X] Create a temp playlist starting at current media till last media of playlist
'   [X] Make VLC run this temp playlist
' [X] VLC listener (every 2s)
'   [X] check last played media in the .ini, update g_LastMf + listview accordingly
'   [X] check if VLC has been killed -> if yes stop VLC listener

'------------------------------------------------------------------------------
GLOBAL g_VlcPath AS STRING ' vlc path
GLOBAL g_VlcCfg  AS STRING ' vlc config file path
GLOBAL g_LastPl  AS STRING ' the last playlist opened
GLOBAL g_TmpPl   AS STRING ' the local temp playlist created by VPM
GLOBAL g_LastMf  AS STRING ' the last media file played from the current playlist
GLOBAL g_Mf()    AS STRING ' the list of media files for the current playlist
GLOBAL g_TmpDir  AS STRING ' temp dir where to store program files
GLOBAL g_OnUnix  AS LONG   ' if we are on unix or not
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB LogMe(BYVAL lg AS STRING)
    ' Only produce logs during dev:
    IF NOT EXISTS(LEFT$(EXE.FULL$,-3) + "log") THEN EXIT SUB

    LOCAL ff AS LONG
    LOCAL d, t AS STRING

    ' Create TimeStamp
    d = DATE$
    d = RIGHT$(d,4) + LEFT$(d,2) + MID$(d, 4, 2)
    t = TIME$
    REPLACE ":" WITH "" IN t
    t = d + "-" + t + ","
    t = t + FORMAT$((TIMER*1000) MOD 1000, "000")

    ' Write to log file
    ff = FREEFILE
    OPEN LEFT$(EXE.FULL$,-3) + "log" FOR APPEND AS #ff
    PRINT #ff, "[" + t + "] " + lg
    CLOSE #ff
END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB AutoDetectVlc()
    LOCAL e AS STRING

    ' Specific if we run from Unix/Wine
    IF g_OnUnix THEN
        LET g_VlcPath = GetUnixRet("whereis vlc")
        IF LEN(g_VlcPath) THEN
            ' Parse the vlc path from the string "vlc: /usr/bin/vlc /usr/lib/x86_64-linux-gnu/vlc ..."
            g_VlcPath = TRIM$(MID$(g_VlcPath, INSTR(g_VlcPath,":") + 1))
            IF INSTR(g_VlcPath, " ") THEN g_VlcPath = LEFT$(g_VlcPath, INSTR(g_VlcPath," ") - 1)
        END IF
        IF g_VlcPath = "" AND UnixExists("/usr/bin/vlc") THEN g_VlcPath = "/usr/bin/vlc"
        EXIT SUB
    END IF

    ' Run from Windows: get VLC path from registry
    LET g_VlcPath = GETREGVALUE(%HKEY_LOCAL_MACHINE, "SOFTWARE\VideoLAN\VLC", "")
    IF g_VlcPath <> "" THEN EXIT SUB

    ' Not found in default key > try another key
    LET g_VlcPath = GETREGVALUE(%HKEY_LOCAL_MACHINE, "SOFTWARE\VideoLAN\VLC", "InstallDir")
    IF g_VlcPath <> "" THEN g_VlcPath += "\vlc.exe" : EXIT SUB

    ' Not found via registry > try a manual method
    g_VlcPath = "C:\Program Files (x86)\VideoLAN\VLC\"
    e = DIR$(g_VlcPath + "vlc.exe") : DIR$ CLOSE
    IF e <> "" THEN g_VlcPath += e : EXIT SUB

    ' Last attempt...
    g_VlcPath = "C:\Program Files\VideoLAN\VLC\"
    e = DIR$(g_VlcPath + "vlc.exe") : DIR$ CLOSE
    IF e <> "" THEN g_VlcPath += e : EXIT SUB

    ' Definitely not found :(
    g_VlcPath = ""

END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION GetVlcLastPlayedMedia() AS STRING
    LOCAL zstr AS ASCIIZ * %MAX_PATH
    LOCAL r AS STRING
    LOCAL i AS LONG
    GetPrivateProfileString "RecentsMRL", "list", "", zstr, %MAX_PATH, (g_VlcCfg)
    i = INSTR(zstr, ", ")
    IF i > 0 THEN zstr = LEFT$(zstr, i-1)
    r = TRIM$(zstr)
    r = LTRIM$(r, "file://")
    r = LTRIM$(r, "/")
    r = LTRIM$(r, "home/" + ENVIRON$("USERNAME") + "/")
'    LogMe "GetVlcLastPlayedMedia() returned: " + r
    FUNCTION = r
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION PBMAIN () AS LONG
    LOCAL e  AS STRING
    LOCAL ff AS LONG

    ' Create exe icon in Unix dock and Unix file explorer
    Me2Unix 1, 1, "vpm" ' askConfirmation=%TRUE, showInAppMenu=%TRUE, dkpName="wine-vpm.desktop"

    ' Are we on Unix/Wine?
    g_OnUnix = IsOnUnix()
    LogMe STRING$(80, "-")
    LogMe "Starting " + EXE.NAME$ + " from " + EXE.PATH$
    LogMe "IsOnUnix() returned " + IIF$(g_OnUnix, "TRUE", "False")

    ' Set temp and vlc config paths
    IF g_OnUnix THEN
        g_TmpDir = "C:\Windows\Temp\"
        g_VlcCfg = UNIX_HOME + ".config\vlc\vlc-qt-interface.conf"
    ELSE
        g_TmpDir = LocalAppData
        g_VlcCfg = RoamingAppData + "vlc\vlc-qt-interface.ini"
    END IF
    LogMe "Reading VLC prefs from: " + g_VlcCfg
    LogMe "Storing temp playlists in: " + g_TmpDir

    ' Detect VLC path
    AutoDetectVlc()
    IF g_VlcPath = "" THEN
        MYMSGBOX 0, "VLC not detected on your system",EXE.NAME$,%MB_ICONERROR
        EXIT FUNCTION
    END IF
    LogMe "VLC player detected at: " + g_VlcPath

    ' Get last playlist, if any
    e = g_TmpDir + EXE.NAME$ + ".last.pl"
    IF EXISTS(e) THEN
        ff = FREEFILE
        OPEN e FOR INPUT AS #ff
        LINE INPUT #ff, g_LastPl
        CLOSE #ff
    END IF
    LogMe "Last playlist: " + g_LastPl

    ' Show main dialog
    ShowDialog 0

END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION IsVlcAlive() AS LONG
' Search for a VLC instance currently running
    LOCAL hWnd AS DWORD
    LOCAL length AS LONG
    LOCAL e AS STRING

    IF g_OnUnix THEN
        FUNCTION = 1
'        LogMe "- [vlc listener] IsVlcAlive() returned: True"
        EXIT FUNCTION
    END IF

    FUNCTION = 0
    hWnd = FindWindow("Qt5QWindowIcon", "") ' VLC dialog class
    IF hWnd = 0 THEN EXIT FUNCTION
    length = SendMessage(hWnd, %WM_GETTEXTLENGTH, 0, 0) + 1    ' Get VLC caption length
    e = SPACE$(length)
    length = SendMessage(hWnd, %WM_GETTEXT, length, STRPTR(e)) ' Get VLC caption
    LogMe "- [vlc listener] IsVlcAlive() returned: " + IIF$(INSTR(e,"VLC")=0,"FALSE","True")
    FUNCTION = (INSTR(e, "VLC") <> 0)

END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
THREAD FUNCTION VlcListener(BYVAL hDlg AS DWORD) AS DWORD
    LOCAL curr_vid  AS STRING
    LOCAL last_vid  AS STRING
    LOCAL found     AS LONG

    curr_vid = g_LastMf

    DO
        SLEEP 2000
        IF ISFALSE IsVlcAlive() THEN EXIT LOOP
        last_vid = GetVlcLastPlayedMedia()
        IF last_vid = "" THEN
            CONTROL SHOW STATE hDlg, 4002, %SW_RESTORE ' VLC last media file not accessible
        ELSE
            CONTROL SHOW STATE hDlg, 4002, %SW_HIDE
            IF last_vid <> curr_vid THEN                ' VLC changed the media played
                LogMe "- [vlc listener] detected media change by vlc: from " + curr_vid + " to " + last_vid
'                LISTVIEW FIND EXACT hDlg, 2001, 1, FShort(last_vid) TO found
                ARRAY SCAN g_Mf(), =last_vid, TO found
                IF found THEN
                    g_LastMf = last_vid
                    DIALOG POST hDlg, %WM_UPDATEMF, 0, 0
                    curr_vid = last_vid
                END IF
            END IF
        END IF
    LOOP

    FUNCTION = 1 ' done
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION DqProtect(s AS STRING) AS STRING
    IF INSTR(s, " ") > 0 THEN FUNCTION = $DQ + s + $DQ ELSE FUNCTION = s
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB UpdateLV(BYVAL hDlg AS DWORD)
    LOCAL i, found AS LONG
'    LISTVIEW FIND EXACT hDlg, 2001, 1, FShort(g_LastMf) TO found
    ARRAY SCAN g_Mf(), =g_LastMf, TO found
    IF found = 0 THEN EXIT SUB
    FOR i = LBOUND(g_Mf) TO UBOUND(g_Mf)
        IF i < found THEN
            LISTVIEW SET IMAGE hDlg, 2001, i, 2 ' played
        ELSEIF i = found THEN
            LISTVIEW SET IMAGE hDlg, 2001, i, 1 ' play
            LISTVIEW SELECT hDlg, 2001, i
            LISTVIEW VISIBLE hDlg, 2001, i
        ELSE
            LISTVIEW SET IMAGE hDlg, 2001, i, 0 ' no icon (media yet to be played)
        END IF
    NEXT
END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION CreatePersistentLocalM3uFromFolder(BYVAL folder AS STRING) AS STRING
    LOCAL ff, i AS LONG
    LOCAL pl, e AS STRING
    RDIR_DeclareLocals

    LogMe "Entering CreatePersistentLocalM3uFromFolder()"

    RDIR_ListFilesIn(folder, "*.*")

    ' Dump all files path + name to g_Mf()
    REDIM g_Mf(LBOUND(RDIR_FileData) TO UBOUND(RDIR_FileData)) AS STRING
    FOR i = LBOUND(RDIR_FileData) TO UBOUND(RDIR_FileData)
        g_Mf(i) = RDIR_File(i)
    NEXT
    LogMe "- " + FORMAT$(i) + " file paths dumped to g_Mf()"

    ' Restrict g_Mf() to video files only
    FOR i = UBOUND(g_Mf) TO LBOUND(g_Mf) STEP -1
        IF INSTR($VIDEXT, ","+GetExt(g_Mf(i))+",") = 0 THEN ArrDel g_Mf(), i
    NEXT i
    LogMe "- g_Mf() now purged from non-video files: new size is " + FORMAT$(UBOUND(g_Mf))

    ' Sort g_Mf() alphabetically
    ARRAY SORT g_Mf()

    ' Create local m3u playlist
    IF g_OnUnix THEN
        pl = UNIX_HOME + PATHNAME$(NAMEX, RTRIM$(folder,"\")) + ".m3u"
    ELSE
        pl = g_TmpDir + PATHNAME$(NAMEX, RTRIM$(folder,"\")) + ".m3u"
    END IF
    LogMe "- creating persistent m3u playlist: " + pl
    ff = FREEFILE
    OPEN pl FOR OUTPUT AS #ff
    PRINT #ff, "#EXTM3U"
    FOR i = LBOUND(g_Mf) TO UBOUND(g_Mf)
        PRINT #ff, "#EXTINF:," + FName(g_Mf(i))
        e = FFull(g_Mf(i))
        IF i = LBOUND(g_Mf) OR i = UBOUND(g_Mf) THEN LogMe "  o " + FName(g_Mf(i)) + " @" + e
        IF i = LBOUND(g_Mf) THEN LogMe "  o [...]"
        PRINT #ff, e
    NEXT
    CLOSE #ff

    LogMe "- persistent m3u playlist successfully created"
    LogMe "CreatePersistentLocalM3uFromFolder() finished correctly"

    FUNCTION = pl
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB CreateLocalM3uStartingFromSel(BYVAL hDlg AS DWORD)
    LOCAL i, k, ff AS LONG
    LOCAL e AS STRING
'    LOCAL p AS STRING

    LogMe "Entering CreateLocalM3uStartingFromSel()"

    ' Get selected media
    LISTVIEW GET SELECT hDlg, 2001 TO k
    LISTVIEW GET USER hDlg, 2001, k TO k

'    ' Create absolute path if needed
'    p = FPath(g_LastPl)

    ' Create local m3u playlist
    ff = FREEFILE
    IF g_OnUnix THEN
        g_TmpPl = UNIX_HOME + "vpm.m3u"
    ELSE
        g_TmpPl = g_TmpDir + FName(g_LastPl) + ".m3u"
    END IF
    LogMe "- creating temp m3u playlist: " + g_TmpPl
    OPEN g_TmpPl FOR OUTPUT AS #ff
    PRINT #ff, "#EXTM3U"
    FOR i = LBOUND(g_Mf) TO UBOUND(g_Mf)
        IF i >= k THEN
            PRINT #ff, "#EXTINF:," + FName(g_Mf(i))
            e = g_Mf(i) ' Already encoded via FFull()
            IF i = LBOUND(g_Mf) OR i = UBOUND(g_Mf) THEN LogMe "  o " + FName(g_Mf(i)) + " @" + e
            IF i = LBOUND(g_Mf) THEN LogMe "  o [...]"
            PRINT #ff, e
        END IF
    NEXT
    CLOSE #ff

    IF g_OnUnix THEN
        KILL g_TmpDir + FName(g_LastPl) + ".m3u" ' backup just to know the last media played
        FILECOPY g_TmpPl, g_TmpDir + FName(g_LastPl) + ".m3u"
        g_TmpPl = "/home/" + ENVIRON$("USERNAME") + "/vpm.m3u"
    END IF

    LogMe "- temp m3u playlist successfully created"
    LogMe "CreateLocalM3uStartingFromSel() finished correctly"
END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
MACRO LaunchLocalM3u
    MACROTEMP lRes
    DIM lRes AS LONG
    LogMe "Entering LaunchLocalM3u(M)"
    LogMe "- executing: " + g_VlcPath + " --no-random " + DqProtect(g_TmpPl)
    IF g_OnUnix THEN
        UnixShell g_VlcPath + " --no-random " + DqProtect(g_TmpPl)
    ELSE
        lRes = SHELL(g_VlcPath + " --no-random " + DqProtect(g_TmpPl))
    END IF
    IF hThread = 0 THEN
        THREAD CREATE VlcListener(CB.HNDL) TO hThread
        LogMe "- vlc listener started: thread id #" + FORMAT$(hThread)
        idTimer = SetTimer(CB.HNDL, 999, 2000, BYVAL 0)
        CONTROL SHOW STATE CB.HNDL, 4001, %SW_RESTORE ' VLC Listener thread active
    END IF
    LogMe "LaunchLocalM3u(M) finished"
END MACRO
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
MACRO KillVlcListener
    LogMe "Killing vlc listener..."
    FOR i = 1 TO 3
        CONTROL SHOW STATE CB.HNDL, 4001, %SW_HIDE
        CONTROL REDRAW CB.HNDL, 4001 : DIALOG DOEVENTS
        SLEEP 600
        CONTROL SHOW STATE CB.HNDL, 4001, %SW_RESTORE
        CONTROL REDRAW CB.HNDL, 4001 : DIALOG DOEVENTS
        SLEEP 600
    NEXT
    THREAD CLOSE hThread TO i
    hThread = 0
    KillTimer CB.HNDL, idTimer
    idTimer = 0
    CONTROL SHOW STATE CB.HNDL, 4001, %SW_HIDE
END MACRO
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
CALLBACK FUNCTION ProcDialog
    STATIC hThread AS DWORD
    STATIC idTimer AS LONG
    LOCAL xt AS STRING
    LOCAL e  AS STRING
    LOCAL i  AS LONG

    CB_SAVEPOS      ' Save & restore dialog position
    M_SELF_UPDATE   ' Handle program self update

    SELECT CASE AS LONG CB.MSG

        ' Initialization handler
        ' ---------------------------------------------
        CASE %WM_INITDIALOG
            LoadRecentList CB.HNDL
            DIALOG POST CB.HNDL, %WM_LOADPL, 0, 0

        ' User message: load last playlist, if any
        ' ---------------------------------------------
        CASE %WM_LOADPL
            e = TRIM$(COMMAND$)
            IF e <> "" AND EXISTS(e) THEN        ' ...passed as argument
                LogMe "Loading playlist passed as argument: " + e
                LoadPlaylist CB.HNDL, e
            ELSEIF g_LastPl <> "" THEN
                LogMe "Loading playlist from last session: " + g_LastPl
                LoadPlaylist CB.HNDL, g_LastPl    ' ...or from last session
            END IF

        ' User message: start playlist from the top
        ' ---------------------------------------------
        CASE %WM_STARTPL
            CreateLocalM3uStartingFromSel CB.HNDL
            CONTROL SET TEXT CB.HNDL, 3001, "Resume playlist"
            LaunchLocalM3u

        ' User message: resume playlist
        ' ---------------------------------------------
        CASE %WM_RESUMEPL
            CreateLocalM3uStartingFromSel CB.HNDL
            LaunchLocalM3u

        ' User message: last played media file changed
        ' ---------------------------------------------
        CASE %WM_UPDATEMF
            UpdateLV CB.HNDL
            CreateLocalM3uStartingFromSel CB.HNDL

        ' Drag and drop playlist on program to open it
        ' ---------------------------------------------
        CASE %WM_DROPFILES
            IF DragQueryFile(CB.WPARAM, -1, BYVAL 0, 0) > 1 THEN EXIT FUNCTION ' ignore multiple
            LOCAL zStr AS ASCIIZ * %MAX_PATH
            DragQueryFile CB.WPARAM, i, zStr, SIZEOF(zStr)
            e = TRIM$(zStr)
            DragFinish CB.WPARAM
            LogMe "User dragged and dropped: " + e
            xt = GetExt(e)
            SELECT CASE xt
            ' Valid playlist > load it!
            CASE ".m3u", ".m3u8", ".xspf"
                LoadPlaylist CB.HNDL, e
            CASE ELSE
            ' Else: test if it's a folder
                IF (GETATTR(e) AND %SUBDIR) = %SUBDIR THEN
                    e = CreatePersistentLocalM3uFromFolder(e)
                    LoadPlaylist CB.HNDL, e
                END IF
            END SELECT

        ' Timer (2s) to check on VLC Listener thread
        ' ---------------------------------------------
        CASE %WM_TIMER
            IF hThread <> 0 THEN
                THREAD STATUS hThread TO i
                IF i <> &H103 THEN
                    KillVlcListener
                END IF
            END IF

        ' Double-click on listview item to lauch media file
        ' --------------------------------------------------
        CASE %WM_NOTIFY
            LOCAL pLV AS LVUNION PTR
            pLV = CB.LPARAM
            IF 2001 = @pLV.NMHDR.IDFROM _
            AND %NM_DBLCLK = @pLV.NMHDR.CODE THEN
                ' Force-stop VLC Listener to avoid resuming an old "last played media"
                KillVlcListener
                ' Set double-clicked media as last media file
                LISTVIEW GET SELECT CB.HNDL, 2001 TO i
                LISTVIEW GET USER CB.HNDL, 2001, i TO i
                g_LastMf = g_Mf(i)
                ' Update the listview, create and start local playlist
                UpdateLV CB.HNDL
                CreateLocalM3uStartingFromSel CB.HNDL
                LaunchLocalM3u
            END IF

        CASE %WM_COMMAND                ' Process control notifications
            SELECT CASE AS LONG CB.CTL

                ' Double-click on "Playlists" label: secret feature!
                ' -----------------------------------------------------------------
                CASE 1001
                    IF CB.CTLMSG = %STN_DBLCLK THEN
                        i = SHELL("notepad " + g_TmpDir + EXE.NAME$ + ".lst")
                    END IF

                ' New selection in "Recent playlists" combobox: change playlist
                ' -----------------------------------------------------------------
                CASE 1002
                    IF CB.CTLMSG = %CBN_SELENDOK THEN
                        COMBOBOX GET SELECT CB.HNDL, CB.CTL TO i
                        COMBOBOX GET TEXT CB.HNDL, CB.CTL, i TO e
                        LogMe "New recent playlist picked by user! " + $DQ + e +$DQ
                        IF LEFT$(e, 1) = "<" THEN EXIT FUNCTION ' <none loaded>
                        LoadPlaylist CB.HNDL, e
                    END IF

                ' Button "Load/Change playlist"
                ' -----------------------------------------------------------------
                CASE 1003
                    IF CB.CTLMSG = %BN_CLICKED OR CB.CTLMSG = 1 THEN
                        e = CHR$("VLC playlist (*.m3u *.m3u8 *.xspf)", 0)
                        e += CHR$("*.m3u;*.m3u8;*.xspf", 0)
                        DISPLAY OPENFILE CB.HNDL, -120, 0, "Load playlist", "", e, _
                            "", "", %OFN_FILEMUSTEXIST TO e
                        IF e = "" THEN EXIT FUNCTION
                        LoadPlaylist CB.HNDL, e
                    END IF

                ' Button "Start/Resume playlist"
                ' -----------------------------------------------------------------
                CASE 3001
                    CreateLocalM3uStartingFromSel CB.HNDL
                    CONTROL SET TEXT CB.HNDL, 3001, "Resume playlist"
                    LaunchLocalM3u

                ' Button "Help"
                ' -----------------------------------------------------------------
                CASE 3002
                    ShellExecute %NULL, "open", "http://mougino.free.fr/vpm/", "", "", %SW_SHOW

                ' VLC Listener ("eye" icon)
                ' -----------------------------------------------------------------
                CASE 4001
                    IF CB.CTLMSG = %STN_DBLCLK THEN
                        KillVlcListener
                    END IF

            END SELECT
    END SELECT
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB ParsePlaylist(BYVAL file AS STRING, BYREF arr() AS STRING)
' Parse a playlist and return an array of its media files
    LOCAL ff AS LONG
    LOCAL n  AS LONG
    LOCAL e  AS STRING

    IF NOT EXISTS(file) THEN EXIT SUB
    ff = FREEFILE
    OPEN file FOR INPUT ACCESS READ LOCK SHARED AS #ff

    IF RIGHT$(LCASE$(file),5) = ".xspf" THEN
        DO
            LINE INPUT #ff, e : e = TRIM$(e, ANY $SPC+$TAB)
            IF INSTR(e, "<location>") <> 1 THEN ITERATE DO
            e = LTRIM$(e, "<location>")
            e = RTRIM$(e, "</location>")
            IF n = 0 THEN DIM arr(0) ELSE REDIM PRESERVE arr(n)
            arr(n) = e
            INCR n
        LOOP UNTIL EOF(#ff)

    ELSEIF RIGHT$(LCASE$(file),4) = ".m3u" OR RIGHT$(LCASE$(file),5) = ".m3u8" THEN
        DO
            LINE INPUT #ff, e
            IF LEFT$(e,1) = "#" THEN ITERATE DO
            IF n = 0 THEN DIM arr(0) ELSE REDIM PRESERVE arr(n)
            arr(n) = e
            INCR n
        LOOP UNTIL EOF(#ff)
    END IF
    CLOSE #ff
END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB ArrDel(BYREF arr() AS STRING, BYVAL idx AS LONG)
' Remove an array entry at index #idx and redim/preserve the rest of the array
    IF idx < LBOUND(arr) OR idx > UBOUND(arr) THEN EXIT SUB
    LOCAL j AS LONG
    FOR j = idx TO UBOUND(arr)-1
        arr(j) = arr(j+1)
    NEXT
    REDIM PRESERVE arr(LBOUND(arr) TO UBOUND(arr)-1)
END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION GetExt(filePath AS STRING) AS STRING
' Return lower case extension for a file, if any, including the dot
    LOCAL i AS LONG
    i = INSTR(-1, filePath, ".")
    IF i = 0 THEN EXIT FUNCTION
    FUNCTION = LCASE$(MID$(filePath, i))
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION FFull(filePath AS STRING) AS STRING
' Return the url-encoded full file path in order to populate an .m3u playlist
    LOCAL r AS STRING
    r = filePath

    IF g_OnUnix THEN
        r = ".wine/dosdevices/" + LCASE$(LEFT$(r,1)) + MID$(r,2)
    ELSE
        r = "file:///" + r
    END IF

    REPLACE "%" WITH "%25" IN r
    REPLACE ":" WITH "%3A" IN r
    REPLACE " " WITH "%20" IN r
    REPLACE "\" WITH "/" IN r

    FUNCTION = r
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION FShort(filePath AS STRING) AS STRING
' Opposite of FFull: url-decode and shorten file path to display in listview
    LOCAL r AS STRING
    LOCAL i AS LONG
    r = filePath

    IF INSTR(r, ".wine/dosdevices/") = 1 THEN
        r = MID$(r, LEN(".wine/dosdevices/") + 1)
        r = UCASE$(LEFT$(r,1)) + MID$(r,2)
    ELSEIF INSTR(r, "file:///") = 1 THEN
        r = MID$(r, LEN("file:///") + 1)
    END IF

    REPLACE "/" WITH "\" IN r
    REPLACE "%20" WITH " " IN r
    REPLACE "%3A" WITH ":" IN r
    REPLACE "%25" WITH "%" IN r

    IF LEN(r) > 50 THEN
        i = INSTR(-1, r, "\")
        IF i = 0 THEN i = LEN(r) - 28
        r = LEFT$(r,18) + "..." + MID$(r,i)
    END IF
    FUNCTION = r
END FUNCTION
'------------------------------------------------------------------------------


'------------------------------------------------------------------------------
FUNCTION FPath(filePath AS STRING) AS STRING
' Return url-encoded path for a file, including last slash
    LOCAL r AS STRING
    LOCAL i AS LONG
    r = FShort(filePath)
    i = INSTR(-1, r, "\")
    IF i > 0 THEN r = LEFT$(r, i)
    FUNCTION = FFull(r)
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION FName(filePath AS STRING) AS STRING
' Return file name without extension from a full file uri
    LOCAL r AS STRING
    LOCAL i AS LONG
    r = filePath
    REPLACE "/" WITH "\" IN r
    i = INSTR(-1, r, "\")
    IF i > 0 THEN r = MID$(r, i+1)
    i = INSTR(-1, r, ".")
    IF i > 0 THEN r = LEFT$(r, i-1)
    FUNCTION = LTRIM$(FShort(r), ".")
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION PathEnv(BYVAL filePath AS STRING) AS STRING
' Return full file path with ENV variables "%TMP%" or "%LocalAppData%" if any
    IF g_OnUnix THEN
        REPLACE g_TmpDir WITH "%TMP%\" IN filePath
        REPLACE UNIX_HOME WITH "~\" IN filePath
    ELSE
        REPLACE g_TmpDir WITH "%LocalAppData%\" IN filePath
    END IF
    FUNCTION = filePath
END FUNCTION
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB LoadPlaylist(BYVAL hDlg AS DWORD, BYVAL playlistPath AS STRING)
    LOCAL e     AS STRING
    LOCAL i, ff AS LONG
    LOCAL found AS LONG

    LogMe "Entering LoadPlaylist()"

    ' Reset last played media file
    g_LastMf = ""
    REDIM g_Mf(-1)

    ' Translate playlistPath
    REPLACE "%LocalAppData%\" WITH g_TmpDir IN playlistPath
    REPLACE "%TMP%\" WITH g_TmpDir IN playlistPath
    REPLACE "~\" WITH UNIX_HOME IN playlistPath

    ' Load playlist content into listview
    LISTVIEW RESET hDlg, 2001
    IF NOT EXISTS(playlistPath) THEN
        LogMe "- " + playlistPath + " does not exist > abandoning"
        CONTROL DISABLE hDlg, 3001 ' Start/Resume playlist
        EXIT SUB
    END IF

    ParsePlaylist playlistPath, g_Mf()
    IF UBOUND(g_Mf) < 0 THEN
        CONTROL SET TEXT hDlg, 3001, "Start playlist"
        CONTROL DISABLE hDlg, 3001 ' Start/Resume playlist
        LogMe "- Playlist " + playlistPath + " is invalid!"
        MYMSGBOX hDlg, "Invalid playlist", EXE.NAME$, %MB_ICONWARNING
        EXIT SUB
    END IF
    CONTROL ENABLE hDlg, 3001 ' Start/Resume playlist
    FOR i = UBOUND(g_Mf) TO LBOUND(g_Mf) STEP -1
        IF i = UBOUND(g_Mf) OR i = LBOUND(g_Mf) THEN LogMe "- inserting: " + g_Mf(i)
        IF i = UBOUND(g_Mf) THEN LogMe "- inserting: [...]"
        LISTVIEW INSERT ITEM hDlg, 2001, 1, 0, FShort(g_Mf(i))
        LISTVIEW SET USER hDlg, 2001, 1, i ' pointer to media file path: g_Mf(i)
    NEXT

    ' Save last playlist opened
    g_LastPl = playlistPath
    e = g_TmpDir + EXE.NAME$ + ".last.pl"
    ff = FREEFILE
    OPEN e FOR OUTPUT AS #ff
    PRINT #ff, playlistPath
    CLOSE #ff

    ' Update header combo
    AddToRecentList hDlg, playlistPath

    ' Get last played media file for this playlist, if any
    e = g_TmpDir + FName(playlistPath) + ".m3u"
    IF EXISTS(e) THEN
        ff = FREEFILE
        OPEN e FOR INPUT ACCESS READ LOCK SHARED AS #ff
        LINE INPUT #ff, e ' #EXTM3U
        LINE INPUT #ff, e ' #EXTINF
        LINE INPUT #ff, g_LastMf
        CLOSE #ff
    END IF
    LogMe "- last played media file for this playlist: " + g_LastMf

    ' New playlist >> propose to start it
    ARRAY SCAN g_Mf(), =g_LastMf, TO found
    LogMe "- searching file in listview... detected position: " + FORMAT$(found)
    IF g_LastMf = "" OR found = 0 THEN
        CONTROL SET TEXT hDlg, 3001, "Start playlist"
        LISTVIEW SET IMAGE hDlg, 2001, 1, 1 ' play
        LISTVIEW SELECT hDlg, 2001, 1
        i = MYMSGBOX(hDlg, "Start playlist ?", "", %MB_ICONQUESTION OR %MB_YESNO)
        IF i = %IDYES THEN DIALOG POST hDlg, %WM_STARTPL, 0, 0
        EXIT SUB
    END IF

    ' Ongoing playlist >> mark read files and current file (where to resume)
    CONTROL SET TEXT hDlg, 3001, "Resume playlist"
    FOR i = LBOUND(g_Mf) TO UBOUND(g_Mf)
        IF i < found THEN
            LISTVIEW SET IMAGE hDlg, 2001, i, 2 ' played
        ELSEIF i = found THEN
            LISTVIEW SET IMAGE hDlg, 2001, i, 1 ' play
            LISTVIEW SELECT hDlg, 2001, i
            LISTVIEW VISIBLE hDlg, 2001, i
            EXIT FOR
        END IF
    NEXT
    i = MYMSGBOX(hDlg, "Resume playlist ?", "", %MB_ICONQUESTION OR %MB_YESNO)
    IF i = %IDYES THEN DIALOG POST hDlg, %WM_RESUMEPL, 0, 0

    LogMe "LoadPlaylist() finished"
END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB AddToRecentList(BYVAL hDlg AS DWORD, BYVAL playlistPath AS STRING)
    LOCAL found AS LONG
    LOCAL ff AS LONG

    IF g_OnUnix THEN
        REPLACE g_TmpDir WITH "%TMP%\" IN playlistPath
        REPLACE "~\" WITH UNIX_HOME IN playlistPath
    ELSE
        REPLACE g_TmpDir WITH "%LocalAppData%\" IN playlistPath
    END IF

    COMBOBOX FIND EXACT hDlg, 1002, 1, PathEnv(playlistPath) TO found
    IF found = 0 THEN
        ff = FREEFILE
        OPEN g_TmpDir + EXE.NAME$ + ".lst" FOR APPEND AS  #ff
            PRINT #ff, playlistPath
        CLOSE #ff
    END IF
    LoadRecentList hDlg
END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
SUB LoadRecentList(hDlg AS DWORD)
    LOCAL noproj, found AS LONG
    LOCAL ff AS LONG
    LOCAL e AS STRING

    LogMe "Entering LoadRecentList()"

    COMBOBOX RESET hDlg, 1002
    e = g_TmpDir + EXE.NAME$ + ".lst"

    IF EXISTS(e) THEN
        ff = FREEFILE
        OPEN e FOR INPUT AS  #ff
        DO
            LINE INPUT #ff, e
            IF LEN(e)>0 AND EXISTS(e) THEN
                LogMe "- inserting: " + e
                COMBOBOX INSERT hDlg, 1002, 1, PathEnv(e)
            END IF
        LOOP UNTIL EOF(#ff)
        CLOSE #ff
        COMBOBOX FIND EXACT hDlg, 1002, 1, PathEnv(g_LastPl) TO found
    ELSE
        LogMe "- no recent list to load!"
        COMBOBOX ADD hDlg, 1002, "<none loaded>"
        found = 1
    END IF

    COMBOBOX SELECT  hDlg, 1002, found
    CONTROL SET TEXT hDlg, 1002, PathEnv(g_LastPl)
    LogMe "LoadRecentList() finished"
END SUB
'------------------------------------------------------------------------------

'------------------------------------------------------------------------------
FUNCTION ShowDialog(BYVAL hParent AS LONG) AS LONG
    LOCAL lRes AS LONG
    LOCAL lSty AS LONG
    LOCAL hDlg AS DWORD
    LOCAL hIml AS DWORD

    DIALOG NEW PIXELS, hParent, EXE.NAME$ + $SPC + $VER, , , 340, 380, %WS_POPUP _
        OR %WS_BORDER OR %WS_DLGFRAME OR %WS_SYSMENU OR %WS_CLIPSIBLINGS OR _
        %WS_VISIBLE OR %DS_MODALFRAME OR %DS_3DLOOK OR %DS_NOFAILCREATE OR _
        %DS_SETFONT OR %WS_CAPTION OR %WS_MINIMIZEBOX, %WS_EX_WINDOWEDGE OR _
        %WS_EX_CONTROLPARENT OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR _
        %WS_EX_RIGHTSCROLLBAR, TO hDlg

    CONTROL ADD LABEL,      hDlg, 1001, "Playlist :", 8, 8, 48, 16, %SS_NOTIFY
    CONTROL ADD COMBOBOX,   hDlg, 1002, , 56, 5, 230, 18*10, _
        %CBS_DROPDOWNLIST, %WS_EX_CLIENTEDGE OR %WS_EX_LEFT
    CONTROL ADD BUTTON,     hDlg, 1003, "Load", 286, 5, 48, 22, _
        %BS_CENTER OR %BS_VCENTER OR %WS_GROUP OR %WS_TABSTOP

    CONTROL ADD LISTVIEW,   hDlg, 2001, "", 8, 38, 324, 280, %LVS_NOCOLUMNHEADER OR _
        %LVS_REPORT OR %LVS_SHOWSELALWAYS OR %LVS_SINGLESEL, %WS_EX_CLIENTEDGE
    LISTVIEW INSERT COLUMN  hDlg, 2001, 1, "Media file", 320, %SS_LEFT
    LISTVIEW GET STYLEXX    hDlg, 2001 TO lSty
    LISTVIEW SET STYLEXX    hDlg, 2001, lSty OR %LVS_EX_FULLROWSELECT OR _
        %LVS_EX_GRIDLINES OR %LVS_EX_INFOTIP

    CONTROL ADD BUTTON,     hDlg, 3001, "Start playlist", 8, 330, 150, 40
    CONTROL ADD BUTTON,     hDlg, 3002, "Help", 180, 330, 80, 40

    ' Add status icons
    CONTROL ADD IMAGE,      hDlg, 4001, "ICO4", 312, 354, 16, 16, %SS_NOTIFY ' eye  > VLC listener thread active
    CONTROL SHOW STATE      hDlg, 4001, %SW_HIDE
    CONTROL ADD IMAGE,      hDlg, 4002, "ICO5", 312, 336, 16, 16, %SS_NOTIFY ' (!)  > VLC last media not found
    CONTROL SHOW STATE      hDlg, 4002, %SW_HIDE

    ' Attach play/played imagelist to the listview
    IMAGELIST NEW ICON 16, 16, 24, 6 TO hIml
    IMAGELIST ADD ICON hIml, "ICO2" ' play
    IMAGELIST ADD ICON hIml, "ICO3" ' played
    LISTVIEW SET IMAGELIST  hDlg, 2001, hIml, 1

    DIALOG  SET ICON        hDlg, "ICO1"
    DragAcceptFiles         hDlg, %True
    DIALOG SHOW MODAL       hDlg, CALL ProcDialog TO lRes
    FUNCTION = lRes
END FUNCTION
'------------------------------------------------------------------------------