Public/Show-RpCamera.ps1

function Show-RpCamera {
    <#
    .SYNOPSIS
    Displays a UI for live viewing or playback of camera feeds in a security
    system.
 
    .DESCRIPTION
    The Show-RpCamera function allows users to interact with camera feeds using
    different methods of camera selection. It supports viewing live feeds,
    playback of recorded sequences, and diagnostics overlay on the video feed.
    Users can select cameras by ID, through a direct camera object, or via user
    interface dialogs. Please see link for original script from joshooaj who
    inspired all of RemotePro from sharing this incredible function.
 
    .COMPONENT
    CustomVMSCmdlets
 
    .PARAMETER CameraObject
    Directly pass a camera object to alleviate object dependency between
    threads. Mandatory in the 'CameraObjectSet' parameter set.
 
    .PARAMETER Id
    Specifies the camera IDs as GUIDs. When this parameter is used, camera
    selection dialogs are bypassed. Mandatory in the 'IdSet' parameter set.
 
    .PARAMETER ShowRpItemPicker
    Displays a custom item picker dialog for camera selection. Mandatory in the
    'RpItemPickerSet' parameter set.
 
    .PARAMETER ShowSelectCamera
    Uses the default camera selection dialog. Mandatory in the 'SelectCameraSet'
    parameter set.
 
    .PARAMETER DiagnosticLevel
    Specifies the diagnostic overlay level (0-4) for the video feed. This is
    optional and defaults to '0'.
 
    .PARAMETER SpecifiedDaysForSequences
    Specifies the number of days for which to generate and display sequence data
    from motion-triggered recordings. This parameter is mandatory.
 
    .PARAMETER CheckConnection
    Validates the VMS connection before attempting to show cameras. If the
    connection is invalid, an error dialog is shown. This parameter is optional.
 
    .EXAMPLE
    Show-RpCamera -ShowSelectCamera
 
    This command opens the standard camera selection dialog and displays the
    selected camera feed without any diagnostic overlays.
 
    .EXAMPLE
    Show-RpCamera -Id '12345678-9abc-def0-1234-567890abcdef' -SpecifiedDaysForSequences 7
 
    This command displays the feed of a camera identified by the specified GUID
    with sequence data for the past 7 days.
 
    .EXAMPLE
    Show-RpCamera -ShowRpItemPicker -DiagnosticLevel '3' -SpecifiedDaysForSequences 30
 
    This command uses a custom item picker for camera selection and shows the
    selected camera feed with a high diagnostic level overlay for the past 30
    days.
 
    .NOTES
    - Requires a connection to a VMS (Video Management System).
    - Designed for use in environments where monitoring multiple camera feeds is
    crucial.
    - Flexible camera selection is provided to accommodate different user
    preferences and requirements.
 
    .LINK
    https://gist.github.com/joshooaj/9cf16a92c7e57496b6156928a22f758f
 
    .LINK
    https://www.remotepro.dev/en-US/Show-RpCamera
    #>

    [CmdletBinding(DefaultParameterSetName='RpItemPickerSet')]
    param (
        # Optional to pass in camera object to alleviate object dependency between threads.
        [Parameter(ValueFromPipeline,ParameterSetName='CameraObjectSet', Mandatory=$true)]
        $CameraObject,

        # Specifies the Id of the camera you wish to view. Omit this parameter and you can select a camera from an item selection dialog.
        [Parameter(ValueFromPipelineByPropertyName, ParameterSetName='IdSet', Mandatory=$true)]
        [guid[]]
        $Id,

        # Show RpItemPicker instead of Select-Camera for making selections
        [Parameter(ParameterSetName='RpItemPickerSet', Mandatory=$true)]
        [switch]
        $ShowRpItemPicker,

        # Show Select-Camera instead of Show-RpItemPicker for making selections
        [Parameter(ParameterSetName='SelectCameraSet', Mandatory=$true)]
        [switch]
        $ShowSelectCamera,

        # Specifies the diagnostic overview level to show overlayed onto the image
        [Parameter()]
        [ValidateSet('0','1','2','3','4')]
        [string]
        $DiagnosticLevel = '0',

        # Specifies the amount of Days to generate sequence data from event data for
        # for the given data to create a timespan from the present day.
        [Parameter(Mandatory=$true)]
        [int]
        $SpecifiedDaysForSequences,

        # Validate active connection and display error message window.
        [Parameter(Mandatory=$false)]
        [switch]$CheckConnection
    )

    begin {
        Import-Module -Name RemotePro

        Add-Type -AssemblyName PresentationFramework
        $connectionValid = $true

        if ($CheckConnection) {
            if (-not (Test-RpVmsConnection -ShowErrorDialog $true)) {
                $connectionValid = $false
            }
        }
    }

    process {
        if (-not $connectionValid) {
            Write-Host "Connection validation failed. No VMS connection available."
            return
        }

        if ($PSCmdlet.ParameterSetName -eq 'CameraObjectSet') {
            $cameraItems = $CameraObject
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'IdSet') {
            $cameraItems = $Id | ForEach-Object { Get-VmsCamera -Id $_ | Get-VmsVideoOSItem -Kind Camera }
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'RpItemPickerSet') {
            $cameraItems = Show-RpItemPicker -Title "Custom Item Picker" -Kind @("Camera") -CheckConnection:$CheckConnection
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'SelectCameraSet') {
            $cameraItems = Select-Camera -Title "Select one or more cameras" -OutputAsItem -AllowFolders -AllowServers -RemoveDuplicates
        }

        if ($null -eq $cameraItems -or $cameraItems.Count -eq 0) {
            Write-Error "No camera(s) selected"
            return
        }

        # TODO Condition for selecting entire camera folder - could be reworked into Show-RpItemPicker.
        if ($cameraItems.GetType().FullName -eq "VideoOS.Platform.SDK.Platform.AllRSFolderItem") {
            $cameraItems = $cameraItems.GetChildren()
        }

        foreach ($item in $cameraItems) {
            # Generate last 7 days of recorded squences from motion iniated recordings, store the last to pass into PlaybackWpfUserControl
            # Use VideoOS.Platform.SDK.Platform.CameraItem.FQID.ObjectId for camera Id.
            $sequenceData = Get-VmsCamera -Id $item.FQID.ObjectId | Get-SequenceData -SequenceType MotionSequence -StartTime ([DateTime]::UtcNow).AddDays(-$SpecifiedDaysForSequences)

            # Store the last EvenSequence
            $lastSequence = $sequenceData | Select-Object -last 1

            # Store the start and end sequence dates from the last EventSequnce from EventData.
            $fromSequenceDate = $lastSequence.EventSequence.StartDateTime
            $toSequenceDate = $lastSequence.EventSequence.EndDateTime

            # Add squence "From" and "To" property to camera object.
            $item | Add-Member -MemberType NoteProperty -Name FromSequenceDate -Value $fromSequenceDate -Force
            $item | Add-Member -MemberType NoteProperty -Name ToSequenceDate -Value $toSequenceDate -Force
        }

# 01/25/2025 Note for error handling.
# Replaced Title name from $($MyInvocation.Line) to "Show-RpCamera" due xml
# parse error when using $($MyInvocation.Line) in XAML.
# to type "System.Xml.XmlDocument". Error: "An error occurred while parsing EntityName. Line 7, position 25."
# At line:1 char:38
# + $commandObject | Invoke-RpCommandObject
# + ~~~~~~~~~~~~~~~~~~~~~~
        $xaml = [xml]@"
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mip="clr-namespace:VideoOS.Platform.Client;assembly=VideoOS.Platform"
Title="Show-RpCamera" Height="450" Width="800">
    <TabControl Name="Tabs">
        <TabItem Header="Live" Name="LiveTab">
            <TabItem.Content>
                <UniformGrid Grid.Row="0" Name="LiveGrid" />
            </TabItem.Content>
        </TabItem>
        <TabItem Header="Playback" Name="PlaybackTab">
            <TabItem.Content>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="100"/>
                    </Grid.RowDefinitions>
                        <UniformGrid Grid.Row="0" Name="PlaybackGrid" />
                        <mip:PlaybackWpfUserControl Visibility="Visible" Name="PlaybackControl" Grid.Row="2" ShowTallUserControl="True" ShowTimeSpanControl="True" ShowSpeedControl="True" />
                </Grid>
            </TabItem.Content>
        </TabItem>
        <TabItem Header="LastRecording" Name="LastRecordingTab">
            <TabItem.Content>
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="100"/>
                    </Grid.RowDefinitions>
                        <UniformGrid Grid.Row="0" Name="LastPlaybackGrid" />
                        <mip:PlaybackWpfUserControl Visibility="Visible" Name="PlaybackControl2" Grid.Row="2" ShowTallUserControl="True" ShowTimeSpanControl="True" ShowSpeedControl="True" />
                </Grid>
            </TabItem.Content>
        </TabItem>
    </TabControl>
</Window>
"@

        try {
            $reader = [system.xml.xmlnodereader]::new($xaml)
            $window = [windows.markup.xamlreader]::Load($reader)


            $tabs = [system.windows.controls.tabcontrol]$window.FindName('Tabs')
            $liveGrid = [system.windows.controls.primitives.uniformgrid]$window.FindName('LiveGrid')
            $playbackGrid = [system.windows.controls.primitives.uniformgrid]$window.FindName('PlaybackGrid')
            $LastPlaybackGrid = [system.windows.controls.primitives.uniformgrid]$window.FindName('LastPlaybackGrid')
            $playbackFqid = [VideoOS.Platform.ClientControl]::Instance.GeneratePlaybackController()
            $playbackFqid2 = [VideoOS.Platform.ClientControl]::Instance.GeneratePlaybackController()
            #####$PlaybackControl.SetSequence($lastStartSequenceDate,$lastEndSequenceDate)

            $playbackControl = [videoos.platform.client.PlaybackWpfUserControl]$window.FindName('PlaybackControl')
            $playbackControl2 = [videoos.platform.client.PlaybackWpfUserControl]$window.FindName('PlaybackControl2')


            # https://doc.developer.milestonesys.com/html/MIPhelp/class_video_o_s_1_1_platform_1_1_client_1_1_playback_wpf_user_control.html


            ######$PlaybackControl.SetSequence($StartSequenceDate,$EndSequenceDate)

            $playbackControl.Init($playbackFqid)
            $playbackControl2.Init($playbackFqid2)


            $tabs.Add_SelectionChanged({
                param($source, [system.windows.controls.selectionchangedeventargs]$e)
                if ($e.AddedItems.Count -eq 0 -or $e.RemovedItems.Count -eq 0) {
                    # During startup, this event will be triggered by adding the live/playback tabitems to the tabcontrol.
                    return
                }

                # When user switches from live to playback and back, we disconnect the live/playback view items to minimize bandwidth/resource usage
                $selected = [system.windows.controls.tabitem]$e.AddedItems[0]
                $deselected = [system.windows.controls.tabitem]$e.RemovedItems[0]
                foreach ($viewer in $selected.FindName("$($selected.Header)Grid").Children) {
                    Write-Host "$viewer"
                    $viewer.Connect()
                }
                foreach ($viewer in $deselected.FindName("$($deselected.Header)Grid").Children) {
                    $viewer.Disconnect()
                }
            })


            foreach ($item in $cameraItems) {
                $liveViewer = [videoos.platform.client.imageviewerwpfcontrol]::new()
                $liveViewer.CameraFQID = $item.FQID
                $liveViewer.Initialize()
                $liveViewer.EnableDigitalZoom = $true
                $liveViewer.EnableMouseControlledPtz = $true
                $liveViewer.AdaptiveStreaming = $true
                $liveViewer.Connect()

                # https://doc.developer.milestonesys.com/html/MIPhelp/class_video_o_s_1_1_platform_1_1_client_1_1_playback_wpf_user_control.html#a5871fb628d6ffe678f479573b122dde2
                #$playbackControl.SelectionToTime = $item.ToSequenceDate
                # https://doc.developer.milestonesys.com/html/MIPhelp/class_video_o_s_1_1_platform_1_1_client_1_1_playback_wpf_user_control.html#ab62f27a35d782b25a4608b6b14760d31
                #$playbackControl.SelectionFromTime = $item.FromSequenceDate


                $playbackControl.SetCameras($item.FQID)

                $playbackControl.ShowTallUserControl
                $playbackControl.ShowTimeSpanControl
                $playbackControl.ShowSpeedControl

                #$playbackControl.ShowTallUserControl.IsChecked.Value
                #$playbackControl.ShowTimeSpanControl.IsChecked.Value
                #$playbackControl.ShowSpeedControl.IsChecked.Value
                # https://doc.developer.milestonesys.com/html/MIPhelp/class_video_o_s_1_1_platform_1_1_client_1_1_image_viewer_wpf_control.html
                $playbackViewer = [videoos.platform.client.imageviewerwpfcontrol]::new()



                $playbackViewer.PlaybackControllerFQID = $playbackFqid
                $playbackViewer.CameraFQID = $item.FQID
                $playbackViewer.Initialize()


                $playbackViewer.EnableDigitalZoom = $true
                $playbackViewer.EnableMouseControlledPtz = $true
                $playbackViewer.EnableBrowseMode = $true



                $playbackControl2.SetCameras($item.FQID)

                if ($SpecifiedDaysForSequences) {
                    $playbackControl2.SetSequence($item.FromSequenceDate,$item.ToSequenceDate)
                }

                $playbackControl2.ShowTallUserControl = $true
                $playbackControl2.ShowTimeSpanControl = $true
                $playbackControl2.ShowSpeedControl = $true
                $playbackViewerLastRecording = [videoos.platform.client.imageviewerwpfcontrol]::new()
                $playbackViewerLastRecording.PlaybackControllerFQID = $playbackFqid2
                $playbackViewerLastRecording.CameraFQID = $item.FQID
                $playbackViewerLastRecording.Initialize()

                $playbackViewerLastRecording.EnableDigitalZoom = $true
                $playbackViewerLastRecording.EnableMouseControlledPtz = $true
                $playbackViewerLastRecording.EnableBrowseMode = $true


                $liveGrid.AddChild($liveViewer)
                $playbackGrid.AddChild($playbackViewer)
                $LastPlaybackGrid.AddChild($playbackViewerLastRecording)

            }
            [videoos.platform.environmentmanager]::Instance.EnvironmentOptions.PlayerDiagnosticLevel = $DiagnosticLevel
            [videoos.platform.environmentmanager]::Instance.FireEnvironmentOptionsChangedEvent()
            [videoos.platform.environmentmanager]::Instance.SendMessage([videoos.platform.messaging.message]::new([videoos.platform.messaging.messageid+system]::ModeChangeCommand, [videoos.platform.Mode]::ClientPlayback), $playbackFqid)
            [videoos.platform.environmentmanager]::Instance.SendMessage([videoos.platform.messaging.message]::new([videoos.platform.messaging.messageid+system]::ModeChangeCommand, [videoos.platform.Mode]::ClientPlayback), $playbackFqid2)
            $null = $window.ShowDialog()

        }
        finally {
            foreach ($child in $liveGrid.Children + $playbackGrid.Children + $LastPlaybackGrid.Children) {
                $child.Disconnect()
                $child.Dispose()
            }
            if ($null -ne $playbackControl -or $null -ne $playbackControl2) {
                $playbackControl.Close()
                $playbackControl2.Close()
            }
            if ($window) {
                #ReleasePlaybackController (FQID fqid)
                [VideoOS.Platform.ClientControl]::Instance.ReleasePlaybackController($playbackFqid)
                [VideoOS.Platform.ClientControl]::Instance.ReleasePlaybackController($playbackFqid2)
                #$playbackControl.ReleasePlaybackController($playbackFqid)
                #$playbackControl2.ReleasePlaybackController($playbackFqid2)
                #$playbackControl.ReleasePlaybackController() #added 05/22/24
                #$playbackControl2.ReleasePlaybackController() #added 05/22/24
                $window.Close()
            }
            $reader.Close()
            $reader.Dispose()
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
        }
<#
 # { } else {
        [System.Windows.MessageBox]::Show("No VMS connection.", "Exit", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
    }:Enter a comment or description}
#>

    }
}