functions/Invoke-TuiMp3.ps1

using namespace Terminal.Gui

# A sample MP3 player TUI

function Invoke-TuiMp3 {
    [cmdletbinding()]
    [OutputType("None")]
    [Alias('tuimp3')]
    param(
        [parameter(Position = 0, HelpMessage = 'Specify the path to an MP3 file.')]
        [Alias('Path')]
        [ValidateNotNullOrEmpty()]
        [ValidatePattern('\.mp3$', ErrorMessage = '{0} does not appear to be an MP3 file.')]
        [ValidateScript({ Test-Path -Path $_ }, ErrorMessage = 'Failed to find or validate {0}.')]
        [string]$FilePath,

        [Parameter(HelpMessage = 'Specify the window title.')]
        [validateNotNullOrEmpty()]
        [string]$Title = 'Mp3 Player',

        [Parameter(HelpMessage = "Specify the default folder to open for MP3 files.")]
        [ValidateNotNullOrEmpty()]
        [ValidateScript({ Test-Path -Path $_ }, ErrorMessage = 'Failed to find or validate {0}.')]
        [string]$DefaultLibrary = $HOME
    )

    if ($IsLinux -or $IsMacOS) {
        Write-Warning 'This command requires a Windows platform.'
        return
    }

    #region initialize
    #need this type for the media player
    Add-Type -AssemblyName PresentationCore
    $MediaPlayer = New-Object System.Windows.Media.MediaPlayer

    $ver = $((Get-Module PSTuiTools).version)
    #You MUST invoke Init()
    [Application]::Init()
    #I recommend setting a QuitKey
    [Application]::QuitKey = 'Esc'

    #endregion
    #region create the main window and status bar
    $window = [Window]@{
        Title = $Title
    }

    <#
    add an event handler to stop the player when
    the window closes if running
    #>


    $window.Add_Unloaded({
        if ($script:isPlaying) {
            $MediaPlayer.Stop()
        }
    })

    <# Use this code if you want to customize the Window color scheme
    Valid colors:
        Black
        Blue
        BrightBlue
        BrightCyan
        BrightGreen
        BrightMagenta
        BrightRed
        BrightYellow
        Brown
        Cyan
        DarkGray
        Gray
        Green
        Magenta
        Red
        White
                                       New(Foreground,Background)
        $n = [Terminal.Gui.Attribute]::new('BrightYellow', 'Black')
        $cs = [ColorScheme]::new()
        $cs.normal = $n
        $Window.ColorScheme = $cs
    #>


    #Create a status bar at the bottom of the TUI
    $StatusBar = [StatusBar]::New(
        @(
            [StatusItem]::New('Unknown', $(Get-Date -Format g), {}),
            [StatusItem]::New('Unknown', "v$Ver", {}),
            [StatusItem]::New('Unknown', 'Ready', {})
        )
    )

    #Add the control to the application
    [Application]::Top.Add($StatusBar)

    #endregion

    #region add menu

    #the menu bar actions are running private helper functions

    $MenuItem0 = [MenuItem]::New('_Open File', '', {
        $open = OpenFile -filter .mp3 -path $DefaultLibrary
        if ($open) {
            #update the field if a file was selected
            $txtFile.Text = $open
        }
    })
    $MenuItem1 = [MenuItem]::New('_Quit', '', { quitMP3 })
    $MenuBarItem0 = [MenuBarItem]::New('_File', @($MenuItem0, $MenuItem1))

    $MenuItem3 = [MenuItem]::New('_Documentation', '', { ShowMp3Help })
    $MenuItem4 = [MenuItem]::New('_About', '', { ShowMp3About })
    $MenuBarItem1 = [MenuBarItem]::New('_Help', @($MenuItem3, $MenuItem4))

    $MenuBar = [MenuBar]::New(@($MenuBarItem0, $MenuBarItem1))
    $Window.Add($MenuBar)
    #endregion

    #region add controls

    $txtFile = [TextField]@{
        #X and Y are relative positions in the window
        X           = 1
        Y           = 2
        Width       = [Dim]::Percent(80)
        Height      = 1
        ColorScheme = $window.ColorScheme
        Text        = 'Select an MP3 file from the menu'
    }
    $n = [Terminal.Gui.Attribute]::new('White', 'Blue')
    $cs = [ColorScheme]::new()
    $cs.normal = $n
    $cs.Focus = $n
    $txtFile.ColorScheme = $cs

    $txtFile.Add_TextChanged({ UpdateMP3Info })
    #add the control to the window
    $window.Add($txtFile)

    $progFrame = [FrameView]@{
        X      = 2
        Y      = 4
        Width  = [Dim]::Percent(50)
        Height = 5
        Title  = ''
    }

    $progBar = [ProgressBar]@{
        X                 = 1
        Y                 = 1
        ProgressBarFormat = 'SimplePlusPercentage'
        ProgressBarStyle  = 'Continuous'
        Fraction          = 0
        Width             = [Dim]::Percent(98)
    }

    $progBar.Add_MouseClick({
            param($m)
            $totalSeconds = $MediaPlayer.NaturalDuration.TimeSpan.TotalSeconds
            if ($totalSeconds -gt 0) {
                $clickX = $m.MouseEvent.X
                $fraction = $clickX / $progBar.Frame.Width
                $newPosition = [TimeSpan]::FromSeconds($fraction * $totalSeconds)
                $MediaPlayer.Position = $newPosition
                if ($script:isPlaying) { $MediaPlayer.Play() }
                $progBar.Fraction = $fraction
                [Application]::Refresh()
            }
        }.GetNewClosure())

    $progFrame.Add($progBar)
    # use a timeout token instead
    $script:timeoutToken = $null

    # Define the update action as a scriptblock returning $true to repeat
    $updateProgress = {
        $totalSeconds = $MediaPlayer.NaturalDuration.TimeSpan.TotalSeconds
        if ($totalSeconds -gt 0) {
            $progBar.Fraction = $MediaPlayer.Position.TotalSeconds / $totalSeconds
        }
        else {
            $progBar.Fraction = 0
        }
        $status = '{0} - {1:mm\:ss}' -f $(Split-Path $MediaPlayer.Source.LocalPath -leaf), $MediaPlayer.Position
        $StatusBar.Items[2].Title = $status
        $StatusBar.Items[0].Title = ((Get-Date -format g))
        if ($MediaPlayer.Position.TotalSeconds -ge $totalSeconds) {
            $script:timeoutToken = $null
            $script:isPlaying = $false
            $MediaPlayer.Stop()   # reset position so Play() restarts from beginning
            $progBar.Fraction = 0
            $StatusBar.Items[2].Title = 'Finished'
            [Application]::Refresh()
            return $false  # stop repeating
        }
        return $true  # keep repeating
    }.GetNewClosure()

    $window.Add($progFrame)

    $tvInfo = [TextView]@{
        X         = 62 #$progFrame.Frame.Width + 2
        Y         = $progFrame.Y - 1
        Width     = 60
        Height    = 10
        Multiline = $true
        Visible   = $False
        Text      = @'
'@

    }
    $tvInfo.ColorScheme = $cs

    $window.add($tvInfo)

    $btnPlay = [Button]@{
        X        = 1
        Y        = $progFrame.Frame.Bottom + 2
        Text     = '_Play'
        TabIndex = 0
    }

    #define an action when the button is clicked
    $btnPlay.Add_Clicked({
        $script:isPlaying = $true
        $MediaPlayer.Play()
        $StatusBar.Items[0].Title = $(Get-Date -Format g)
        $StatusBar.Items[2].Title = "Playing $(Split-Path $MediaPlayer.Source.LocalPath -leaf)"
        $progFrame.Title = $script:musicTitle
        # Register a 1-second repeating callback on the main loop
        if ($null -eq $script:timeoutToken) {
            $script:timeoutToken = [Application]::MainLoop.AddTimeout(
                [TimeSpan]::FromSeconds(1), $updateProgress)
        }
        [Application]::Refresh()
    })
    $window.Add($btnPlay)

    $btnPause = [Button]@{
        X        = $btnPlay.Frame.Right + 1
        Y        = $btnPlay.Y
        Text     = 'Pa_use'
        TabIndex = 0
    }
    $btnPause.Add_Clicked({
        $script:isPlaying = $false
        $MediaPlayer.Pause()
        if ($null -ne $script:timeoutToken) {
            [Application]::MainLoop.RemoveTimeout($script:timeoutToken)
            $script:timeoutToken = $null
        }
        $StatusBar.Items[2].Title = "$script:MusicTitle - Paused"
        $StatusBar.Items[0].Title = $(Get-Date -Format g)
        [Application]::Refresh()
    })
    $window.Add($btnPause)
    $btnStop = [Button]@{
        X        = $btnPause.Frame.Right + 1
        Y        = $btnPlay.Y
        Text     = '_Stop'
        TabIndex = 0
    }
    $btnStop.Add_Clicked({
        $script:isPlaying = $false
        $MediaPlayer.Stop()
        if ($null -ne $script:timeoutToken) {
            [Application]::MainLoop.RemoveTimeout($script:timeoutToken)
            $script:timeoutToken = $null
        }
        $progBar.Fraction = 0
        $StatusBar.Items[0].Title = $(Get-Date -Format g)
        $StatusBar.Items[2].Title = 'Stopped'
        [Application]::Refresh()
    })
    $window.Add($btnStop)

    $btnQuit = [Button]@{
        #set the position relative to the Copy button
        X    = $btnStop.Frame.Right + 1
        Y    = $btnPlay.Y
        Text = '_Quit'
    }

    $btnQuit.Add_Clicked({quitMP3})

    $window.Add($btnQuit)

    $txtLyrics = [TextView]@{
        X         = $btnQuit.X +5
        Y         = $btnQuit.Frame.Bottom + 1
        Width     = [Dim]::Percent(95)
        Height    = 25
        Multiline = $True
        Visible   = $false
        WordWrap  = $True
        Text      = 'scooby dooby doo'
    }

    $txtLyrics.ColorScheme = $cs

    $window.Add($txtLyrics)
    #endregion

    #region display
    #Update the form if a file was specified
    if ($PSBoundParameters.ContainsKey('FilePath')) {
        $txtFile.Text = (Convert-Path $PSBoundParameters['FilePath'])
        UpdateMP3Info
    }
    #Add the Window and its nested controls to the TUI application
    [Application]::Top.Add($window)
    #Invoke the TUI
    [Application]::Run()
    #When the TUI ends it will shutdown
    [Application]::ShutDown()

    #endregion
}

#helper functions
function ShowMp3Help {
    #define help information
    [CmdletBinding()]
    param()

    $title = 'TUI MP3 player Help'

    $help = @'
 
 If you didn't specify the path to an MP3 file when you launched this
 TUI, you can select one from the File menu. Use the file dialog to
 navigate to a folder and select an MP3 file.
 
 Select file properties will be displayed.
 
 Use the buttons to play the file. You can also click in the progress
 bar to jump ahead and backwards. The highlighted letters are shortcut
 keys, i.e. Alt+P or Alt+Q.
 
 If the file has lyrics, they will be displayed.
 
 Use the Quit button or menu choice to exit.
'@

    $dialog = [Dialog]@{
        Title         = $title
        TextAlignment = 'Left'
        Width         = 75
        Height        = 30
        Text          = $help
    }
    $ok = [Terminal.Gui.Button]@{
        Text = 'OK'
    }
    $ok.Add_Clicked({ $dialog.RequestStop() })
    $dialog.AddButton($ok)
    [Application]::Run($dialog)

}

#get mp3 information
function getMP3Info {
    [cmdletbinding()]
    param([string]$Path)
    $file = [TagLib.File]::Create((Convert-Path $Path))
    Write-Information $file
    [PSCustomObject]@{
        Path     = $file.Name
        Size     = (Get-Item $File.Name).Length
        Duration = New-TimeSpan -Seconds $file.Properties.Duration.TotalSeconds
        Title    = $file.Tag.Title
        Subtitle = $file.Tag.Subtitle
        Album    = $file.Tag.Album
        Track    = $file.Tag.Track
        Year     = $file.Tag.Year
        Genre    = $file.Tag.JoinedGenres
        Artist   = $file.Tag.JoinedArtists
        Lyrics   = $file.Tag.Lyrics
    }
}

#refresh MP3 info
function updateMP3Info {
    $script:FilePath = $txtFile.Text.toString()
    $script:musicTitle = Split-Path $script:FilePath -Leaf
    $ProgFrame.title = $script:musicTitle
    $MediaPlayer.Open($script:FilePath)
    $StatusBar.Items[2].Title = "Loaded $script:FilePath"

    #update details
    $script:mp3Info = getMP3Info $txtFile.Text.ToString()
    $tvInfo.Text = $script:mp3Info | Format-List Title, Subtitle, Album, Track, Year, Genre, Artist, Duration | Out-String
    $tvInfo.Visible = $True

    if ($script:mp3Info.Lyrics) {
        $txtLyrics.Visible = $True
        #expand lyrics
        $txtLyrics.Text = $script:mp3Info.Lyrics -replace "\r","`n"
    }
    else {
        $txtLyrics.Visible = $False
    }
    [Application]::Refresh()
}

Function quitMP3 {
    if ($MediaPlayer.position.totalSeconds -ge 1) {
        $MediaPlayer.Stop()
    }

    if ($null -ne $script:timeoutToken) {
        [Application]::MainLoop.RemoveTimeout($script:timeoutToken)
        $script:timeoutToken = $null
    }
    [Application]::RequestStop()
}