source_Main.bs

sub Main (args as dynamic) as void
    printRegistry()
    ' The main function that runs when the application is launched.
    m.screen = CreateObject("roSGScreen")
    ' Set global constants
    setConstants()
    ' Write screen tracker for screensaver
    WriteAsciiFile("tmp:/scene.temp", "")
    MoveFile("tmp:/scene.temp", "tmp:/scene")

    m.port = CreateObject("roMessagePort")
    m.screen.setMessagePort(m.port)
    ' Set any initial Global Variables
    m.global = m.screen.getGlobalNode()
    SaveAppToGlobal()
    SaveDeviceToGlobal()
    session.Init()

    ' migrate registry if needed
    m.wasMigrated = false
    runGlobalMigrations()
    runRegistryUserMigrations()
    ' update global LastRunVersion now that migrations are finished
    if m.global.app.version <> m.global.app.lastRunVersion
        set_setting("LastRunVersion", m.global.app.version)
    end if
    if m.wasMigrated then printRegistry()

    m.scene = m.screen.CreateScene("JFScene")
    m.screen.show() ' vscode_rale_tracker_entry

    playstateTask = CreateObject("roSGNode", "PlaystateTask")
    playstateTask.id = "playstateTask"

    sceneManager = CreateObject("roSGNode", "SceneManager")
    sceneManager.observeField("dataReturned", m.port)

    m.global.addFields({ app_loaded: false, playstateTask: playstateTask, sceneManager: sceneManager })
    m.global.addFields({ queueManager: CreateObject("roSGNode", "QueueManager") })
    m.global.addFields({ audioPlayer: CreateObject("roSGNode", "AudioPlayer") })

    app_start:
    ' First thing to do is validate the ability to use the API
    if not LoginFlow() then return

    ' remove login scenes from the stack
    sceneManager.callFunc("clearScenes")

    ' load home page
    group = CreateHomeGroup()
    group.callFunc("loadLibraries")
    stopLoadingSpinner()
    sceneManager.callFunc("pushScene", group)

    m.scene.observeField("exit", m.port)

    ' Downloads and stores a fallback font to tmp:/
    configEncoding = api.system.GetConfigurationByName("encoding")

    if isValid(configEncoding) and isValid(configEncoding.EnableFallbackFont)
        if configEncoding.EnableFallbackFont
            re = CreateObject("roRegex", "Name.:.(.*?).,.Size", "s")
            filename = APIRequest("FallbackFont/Fonts").GetToString()
            if isValid(filename)
                filename = re.match(filename)
                if isValid(filename) and filename.count() > 0
                    filename = filename[1]
                    APIRequest("FallbackFont/Fonts/" + filename).gettofile("tmp:/font")
                end if
            end if
        end if
    end if

    ' has the current user ran this version before?
    usersLastRunVersion = m.global.session.user.settings.lastRunVersion
    if not isValid(usersLastRunVersion) or not versionChecker(m.global.session.user.settings.lastRunVersion, m.global.app.version)
        set_user_setting("LastRunVersion", m.global.app.version)
        ' show what's new popup
        if m.global.session.user.settings["load.allowwhatsnew"]
            dialog = createObject("roSGNode", "WhatsNewDialog")
            m.scene.dialog = dialog
            m.scene.dialog.observeField("buttonSelected", m.port)
        end if
    end if

    ' Handle input messages
    input = CreateObject("roInput")
    input.SetMessagePort(m.port)

    device = CreateObject("roDeviceInfo")
    device.setMessagePort(m.port)
    device.EnableScreensaverExitedEvent(true)
    device.EnableAppFocusEvent(true)
    device.EnableLowGeneralMemoryEvent(true)
    device.EnableLinkStatusEvent(true)
    device.EnableCodecCapChangedEvent(true)
    device.EnableAudioGuideChangedEvent(true)

    ' Check if we were sent content to play with the startup command (Deep Link)
    if isValidAndNotEmpty(args.mediaType) and isValidAndNotEmpty(args.contentId)

        deepLinkVideo = {
            id: args.contentId,
            type: "video"
        }

        m.global.queueManager.callFunc("push", deepLinkVideo)
        m.global.queueManager.callFunc("playQueue")
    end if

    ' This is the core logic loop. Mostly for transitioning between scenes
    ' This now only references m. fields so could be placed anywhere, in theory
    ' "group" is always "whats on the screen"
    ' m.scene's children is the "previous view" stack
    while true
        msg = wait(0, m.port)
        if type(msg) = "roSGScreenEvent" and msg.isScreenClosed()
            print "CLOSING SCREEN"
            return
        else if isNodeEvent(msg, "exit")
            return
        else if isNodeEvent(msg, "closeSidePanel")
            group = sceneManager.callFunc("getActiveScene")
            if group.lastFocus <> invalid
                group.lastFocus.setFocus(true)
            else
                group.setFocus(true)
            end if
        else if isNodeEvent(msg, "quickPlayNode")
            ' measure processing time
            timeSpan = CreateObject("roTimespan")

            group = sceneManager.callFunc("getActiveScene")
            reportingNode = msg.getRoSGNode()
            itemNode = invalid
            if isValid(reportingNode)
                itemNode = reportingNode.quickPlayNode
                reportingNodeType = reportingNode.subtype()
                print "Quick Play reporting node type=", reportingNodeType
                ' prevent double fire bug
                if isValid(reportingNodeType) and (reportingNodeType = "Home" or reportingNodeType = "TVEpisodes")
                    reportingNode.quickPlayNode = invalid
                end if
            end if
            print "Quick Play started. itemNode=", itemNode
            ' if itemNode.json <> invalid
            '     print "itemNode.json=", itemNode.json
            ' end if
            if isValid(itemNode) and isValid(itemNode.id) and itemNode.id <> ""
                ' make sure there is a type and convert type to lowercase
                itemType = invalid
                if isValid(itemNode.type) and itemNode.type <> ""
                    itemType = Lcase(itemNode.type)
                else
                    ' grab type from json and convert to lowercase
                    if isValid(itemNode.json) and isValid(itemNode.json.type)
                        itemType = Lcase(itemNode.json.type)
                    end if
                end if
                print "Quick Play itemNode type=", itemType

                ' can't play the item without knowing what type it is
                if isValid(itemType)
                    startLoadingSpinner()
                    m.global.queueManager.callFunc("clear") ' empty queue/playlist
                    m.global.queueManager.callFunc("resetShuffle") ' turn shuffle off

                    if itemType = "episode" or itemType = "recording" or itemType = "movie" or itemType = "video"
                        quickplay.video(itemNode)
                        ' restore focus
                        if LCase(group.subtype()) = "tvepisodes"
                            if isValid(group.lastFocus)
                                group.lastFocus.setFocus(true)
                            end if
                        end if
                    else if itemType = "audio"
                        quickplay.audio(itemNode)
                    else if itemType = "musicalbum"
                        quickplay.album(itemNode)
                    else if itemType = "musicartist"
                        quickplay.artist(itemNode)
                    else if itemType = "series"
                        quickplay.series(itemNode)
                    else if itemType = "season"
                        quickplay.season(itemNode)
                    else if itemType = "boxset"
                        quickplay.boxset(itemNode)
                    else if itemType = "collectionfolder"
                        quickplay.collectionFolder(itemNode)
                    else if itemType = "playlist"
                        quickplay.playlist(itemNode)
                    else if itemType = "userview"
                        quickplay.userView(itemNode)
                    else if itemType = "folder"
                        quickplay.folder(itemNode)
                    else if itemType = "musicvideo"
                        quickplay.musicVideo(itemNode)
                    else if itemType = "person"
                        quickplay.person(itemNode)
                    else if itemType = "tvchannel"
                        quickplay.tvChannel(itemNode)
                    else if itemType = "program"
                        quickplay.program(itemNode)
                    else if itemType = "photo"
                        quickplay.photo(itemNode)
                    else if itemType = "photoalbum"
                        quickplay.photoAlbum(itemNode)
                    end if
                    m.global.queueManager.callFunc("playQueue")
                end if
            end if
            elapsed = timeSpan.TotalMilliseconds() / 1000
            print "Quick Play finished loading in " + elapsed.toStr() + " seconds."
        else if isNodeEvent(msg, "selectedItem")
            ' If you select a library from ANYWHERE, follow this flow
            selectedItem = msg.getData()
            if isValid(selectedItem)
                startLoadingSpinner()
                selectedItemType = selectedItem.type

                if selectedItemType = "CollectionFolder"
                    if selectedItem.collectionType = "movies"
                        group = CreateMovieLibraryView(selectedItem)
                    else if selectedItem.collectionType = "music"
                        group = CreateMusicLibraryView(selectedItem)
                    else
                        group = CreateItemGrid(selectedItem)
                    end if
                    sceneManager.callFunc("pushScene", group)
                else if selectedItemType = "Folder" and selectedItem.json.type = "Genre"
                    ' User clicked on a genre folder
                    if selectedItem.json.MovieCount > 0
                        group = CreateMovieLibraryView(selectedItem)
                    else
                        group = CreateItemGrid(selectedItem)
                    end if
                    sceneManager.callFunc("pushScene", group)
                else if selectedItemType = "Folder" and selectedItem.json.type = "MusicGenre"
                    group = CreateMusicLibraryView(selectedItem)
                    sceneManager.callFunc("pushScene", group)
                else if selectedItemType = "UserView" or selectedItemType = "Folder" or selectedItemType = "Channel" or selectedItemType = "Boxset"
                    group = CreateItemGrid(selectedItem)
                    sceneManager.callFunc("pushScene", group)
                else if selectedItemType = "Episode" or LCase(selectedItemType) = "recording"
                    ' User has selected a TV episode or Recording they want us to play
                    audio_stream_idx = 0
                    if isValid(selectedItem.selectedAudioStreamIndex) and selectedItem.selectedAudioStreamIndex > 0
                        audio_stream_idx = selectedItem.selectedAudioStreamIndex
                    end if

                    selectedItem.selectedAudioStreamIndex = audio_stream_idx
                    ' Display playback options dialog
                    if selectedItem.json.userdata.PlaybackPositionTicks > 0
                        m.global.queueManager.callFunc("hold", selectedItem)
                        playbackOptionDialog(selectedItem.json.userdata.PlaybackPositionTicks, selectedItem.json)
                    else
                        m.global.queueManager.callFunc("clear")
                        m.global.queueManager.callFunc("push", selectedItem)
                        m.global.queueManager.callFunc("playQueue")
                    end if

                else if selectedItemType = "Series"
                    group = CreateSeriesDetailsGroup(selectedItem.json.id)
                else if selectedItemType = "Season"
                    group = CreateSeasonDetailsGroupByID(selectedItem.json.SeriesId, selectedItem.id)
                else if selectedItemType = "Movie"
                    ' open movie detail page
                    group = CreateMovieDetailsGroup(selectedItem)
                else if selectedItemType = "Person"
                    CreatePersonView(selectedItem)
                else if selectedItemType = "TvChannel" or selectedItemType = "Video" or selectedItemType = "Program"
                    ' User selected a Live TV channel / program
                    ' Show Channel Loading spinner
                    dialog = createObject("roSGNode", "ProgressDialog")
                    dialog.title = tr("Loading Channel Data")
                    m.scene.dialog = dialog

                    ' User selected a program. Play the channel the program is on
                    if LCase(selectedItemType) = "program"
                        selectedItem.id = selectedItem.json.ChannelId
                    end if

                    ' Display playback options dialog
                    showPlaybackOptionDialog = false

                    if isValid(selectedItem.json)
                        if isValid(selectedItem.json.userdata)
                            if isValid(selectedItem.json.userdata.PlaybackPositionTicks)
                                if selectedItem.json.userdata.PlaybackPositionTicks > 0
                                    showPlaybackOptionDialog = true
                                end if
                            end if
                        end if
                    end if

                    if showPlaybackOptionDialog
                        dialog.close = true
                        m.global.queueManager.callFunc("hold", selectedItem)
                        playbackOptionDialog(selectedItem.json.userdata.PlaybackPositionTicks, selectedItem.json)
                    else
                        m.global.queueManager.callFunc("clear")
                        m.global.queueManager.callFunc("push", selectedItem)
                        m.global.queueManager.callFunc("playQueue")
                        dialog.close = true
                    end if

                else if selectedItemType = "Photo"
                    ' only handle selection if it's from the home screen
                    if selectedItem.isSubType("HomeData")
                        print "a photo was selected from the home screen"
                        print "selectedItem=", selectedItem

                        quickplay.photo(selectedItem)
                    end if
                else if selectedItemType = "PhotoAlbum"
                    print "a photo album was selected"
                    print "selectedItem=", selectedItem

                    ' grab all photos inside photo album
                    photoAlbumData = api.users.GetItemsByQuery(m.global.session.user.id, {
                        "parentId": selectedItem.id,
                        "includeItemTypes": "Photo",
                        "Recursive": true
                    })
                    print "photoAlbumData=", photoAlbumData

                    if isValid(photoAlbumData) and isValidAndNotEmpty(photoAlbumData.items)
                        photoPlayer = CreateObject("roSgNode", "PhotoDetails")
                        photoPlayer.itemsArray = photoAlbumData.items
                        photoPlayer.itemIndex = 0
                        m.global.sceneManager.callfunc("pushScene", photoPlayer)
                    end if
                else if selectedItemType = "MusicArtist"
                    group = CreateArtistView(selectedItem.json)
                    if not isValid(group)
                        stopLoadingSpinner()
                        message_dialog(tr("Unable to find any albums or songs belonging to this artist"))
                    end if
                else if selectedItemType = "MusicAlbum"
                    group = CreateAlbumView(selectedItem.json)
                else if selectedItemType = "MusicVideo"
                    group = CreateMovieDetailsGroup(selectedItem)
                else if selectedItemType = "Playlist"
                    group = CreatePlaylistView(selectedItem.json)
                else if selectedItemType = "Audio"
                    m.global.queueManager.callFunc("clear")
                    m.global.queueManager.callFunc("resetShuffle")
                    m.global.queueManager.callFunc("push", selectedItem.json)
                    m.global.queueManager.callFunc("playQueue")
                else
                    ' TODO - switch on more node types
                    stopLoadingSpinner()
                    message_dialog("This type is not yet supported: " + selectedItemType + ".")
                end if
            end if
        else if isNodeEvent(msg, "movieSelected")
            ' If you select a movie from ANYWHERE, follow this flow
            startLoadingSpinner()
            node = getMsgPicker(msg, "picker")
            group = CreateMovieDetailsGroup(node)
        else if isNodeEvent(msg, "seriesSelected")
            ' If you select a TV Series from ANYWHERE, follow this flow
            startLoadingSpinner()
            node = getMsgPicker(msg, "picker")
            group = CreateSeriesDetailsGroup(node.id)
        else if isNodeEvent(msg, "seasonSelected")
            ' If you select a TV Season from ANYWHERE, follow this flow
            startLoadingSpinner()
            ptr = msg.getData()
            ' ptr is for [row, col] of selected item... but we only have 1 row
            series = msg.getRoSGNode()
            if isValid(ptr) and ptr.count() >= 2 and isValid(ptr[1]) and isValid(series) and isValid(series.seasonData) and isValid(series.seasonData.items)
                node = series.seasonData.items[ptr[1]]
                group = CreateSeasonDetailsGroup(series.itemContent, node)
            end if
        else if isNodeEvent(msg, "musicAlbumSelected")
            ' If you select a Music Album from ANYWHERE, follow this flow
            startLoadingSpinner()
            ptr = msg.getData()
            albums = msg.getRoSGNode()
            node = albums.musicArtistAlbumData.items[ptr]
            group = CreateAlbumView(node)
            if not isValid(group)
                stopLoadingSpinner()
            end if
        else if isNodeEvent(msg, "appearsOnSelected")
            ' If you select a Music Album from ANYWHERE, follow this flow
            startLoadingSpinner()
            ptr = msg.getData()
            albums = msg.getRoSGNode()
            node = albums.musicArtistAppearsOnData.items[ptr]
            group = CreateAlbumView(node)
            if not isValid(group)
                stopLoadingSpinner()
            end if
        else if isNodeEvent(msg, "playSong")
            ' User has selected audio they want us to play
            startLoadingSpinner()
            selectedIndex = msg.getData()
            screenContent = msg.getRoSGNode()

            m.global.queueManager.callFunc("resetShuffle")
            m.global.queueManager.callFunc("set", screenContent.albumData.items)
            m.global.queueManager.callFunc("setPosition", selectedIndex)
            m.global.queueManager.callFunc("playQueue")
        else if isNodeEvent(msg, "playItem")
            ' User has selected audio they want us to play
            startLoadingSpinner()
            selectedIndex = msg.getData()
            screenContent = msg.getRoSGNode()

            m.global.queueManager.callFunc("clear")
            m.global.queueManager.callFunc("resetShuffle")
            m.global.queueManager.callFunc("push", screenContent.albumData.items[selectedIndex])
            m.global.queueManager.callFunc("playQueue")
        else if isNodeEvent(msg, "playAllSelected")
            ' User has selected playlist of of audio they want us to play
            screenContent = msg.getRoSGNode()
            startLoadingSpinner()

            m.global.queueManager.callFunc("clear")
            m.global.queueManager.callFunc("resetShuffle")
            m.global.queueManager.callFunc("set", screenContent.albumData.items)
            m.global.queueManager.callFunc("playQueue")
        else if isNodeEvent(msg, "playArtistSelected")
            ' User has selected playlist of of audio they want us to play
            startLoadingSpinner()
            screenContent = msg.getRoSGNode()

            m.global.queueManager.callFunc("clear")
            m.global.queueManager.callFunc("resetShuffle")
            m.global.queueManager.callFunc("set", CreateArtistMix(screenContent.pageContent.id).Items)
            m.global.queueManager.callFunc("playQueue")

        else if isNodeEvent(msg, "instantMixSelected")
            ' User has selected instant mix
            ' User has selected playlist of of audio they want us to play
            screenContent = msg.getRoSGNode()
            startLoadingSpinner()

            viewHandled = false

            ' Create instant mix based on selected album
            if isValid(screenContent.albumData)
                if isValid(screenContent.albumData.items)
                    if screenContent.albumData.items.count() > 0
                        m.global.queueManager.callFunc("clear")
                        m.global.queueManager.callFunc("resetShuffle")
                        m.global.queueManager.callFunc("set", CreateInstantMix(screenContent.albumData.items[0].id).Items)
                        m.global.queueManager.callFunc("playQueue")

                        viewHandled = true
                    end if
                end if
            end if

            if not viewHandled
                ' Create instant mix based on selected artist
                m.global.queueManager.callFunc("clear")
                m.global.queueManager.callFunc("resetShuffle")
                m.global.queueManager.callFunc("set", CreateInstantMix(screenContent.pageContent.id).Items)
                m.global.queueManager.callFunc("playQueue")
            end if

        else if isNodeEvent(msg, "search_value")
            query = msg.getRoSGNode().search_value
            group.findNode("SearchBox").visible = false
            options = group.findNode("SearchSelect")
            options.visible = true
            options.setFocus(true)

            dialog = createObject("roSGNode", "ProgressDialog")
            dialog.title = tr("Loading Search Data")
            m.scene.dialog = dialog
            results = SearchMedia(query)
            dialog.close = true
            options.itemData = results
            options.query = query
        else if isNodeEvent(msg, "itemSelected")
            ' Search item selected
            startLoadingSpinner()
            node = getMsgPicker(msg)
            ' TODO - swap this based on target.mediatype
            ' types: [ Series (Show), Episode, Movie, Audio, Person, Studio, MusicArtist, Recording ]
            if node.type = "Series"
                group = CreateSeriesDetailsGroup(node.id)
            else if node.type = "Movie"
                group = CreateMovieDetailsGroup(node)
            else if node.type = "MusicArtist"
                group = CreateArtistView(node.json)
            else if node.type = "MusicAlbum"
                group = CreateAlbumView(node.json)
            else if node.type = "MusicVideo"
                group = CreateMovieDetailsGroup(node)
            else if node.type = "Audio"
                m.global.queueManager.callFunc("clear")
                m.global.queueManager.callFunc("resetShuffle")
                m.global.queueManager.callFunc("push", node.json)
                m.global.queueManager.callFunc("playQueue")
            else if node.type = "Person"
                group = CreatePersonView(node)
            else if node.type = "TvChannel"
                group = CreateVideoPlayerGroup(node.id)
                sceneManager.callFunc("pushScene", group)
            else if node.type = "Episode"
                group = CreateVideoPlayerGroup(node.id)
                sceneManager.callFunc("pushScene", group)
            else if LCase(node.type) = "recording"
                group = CreateVideoPlayerGroup(node.id)
                sceneManager.callFunc("pushScene", group)
            else if node.type = "Audio"
                selectedIndex = msg.getData()
                screenContent = msg.getRoSGNode()
                m.global.queueManager.callFunc("clear")
                m.global.queueManager.callFunc("resetShuffle")
                m.global.queueManager.callFunc("push", screenContent.albumData.items[node.id])
                m.global.queueManager.callFunc("playQueue")
            else
                ' TODO - switch on more node types
                stopLoadingSpinner()
                message_dialog("This type is not yet supported: " + node.type + ".")
            end if
        else if isNodeEvent(msg, "buttonSelected")
            ' If a button is selected, we have some determining to do
            btn = getButton(msg)
            group = sceneManager.callFunc("getActiveScene")
            if isValid(btn) and btn.id = "play-button"
                ' User chose Play button from movie detail view
                startLoadingSpinner()
                ' Check if a specific Audio Stream was selected
                audio_stream_idx = 0
                if isValid(group) and isValid(group.selectedAudioStreamIndex)
                    audio_stream_idx = group.selectedAudioStreamIndex
                end if

                group.itemContent.selectedAudioStreamIndex = audio_stream_idx
                group.itemContent.id = group.selectedVideoStreamId

                ' Display playback options dialog
                if group.itemContent.json.userdata.PlaybackPositionTicks > 0
                    m.global.queueManager.callFunc("hold", group.itemContent)
                    playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json)
                else
                    m.global.queueManager.callFunc("clear")
                    m.global.queueManager.callFunc("push", group.itemContent)
                    m.global.queueManager.callFunc("playQueue")
                end if

                if isValid(group) and isValid(group.lastFocus) and isValid(group.lastFocus.id) and group.lastFocus.id = "main_group"
                    buttons = group.findNode("buttons")
                    if isValid(buttons)
                        group.lastFocus = group.findNode("buttons")
                    end if
                end if

                if isValid(group) and isValid(group.lastFocus)
                    group.lastFocus.setFocus(true)
                end if

            else if btn <> invalid and btn.id = "trailer-button"
                ' User chose to play a trailer from the movie detail view
                startLoadingSpinner()
                dialog = createObject("roSGNode", "ProgressDialog")
                dialog.title = tr("Loading trailer")
                m.scene.dialog = dialog

                trailerData = api.users.GetLocalTrailers(m.global.session.user.id, group.id)

                if isValid(trailerData) and isValid(trailerData[0]) and isValid(trailerData[0].id)
                    m.global.queueManager.callFunc("clear")
                    m.global.queueManager.callFunc("set", trailerData)
                    m.global.queueManager.callFunc("playQueue")
                    dialog.close = true
                else
                    stopLoadingSpinner()
                end if

                if isValid(group) and isValid(group.lastFocus)
                    group.lastFocus.setFocus(true)
                end if
            else if btn <> invalid and btn.id = "watched-button"
                movie = group.itemContent
                if isValid(movie) and isValid(movie.watched) and isValid(movie.id)
                    if movie.watched
                        UnmarkItemWatched(movie.id)
                    else
                        MarkItemWatched(movie.id)
                    end if
                    movie.watched = not movie.watched
                end if
            else if btn <> invalid and btn.id = "favorite-button"
                movie = group.itemContent
                if movie.favorite
                    UnmarkItemFavorite(movie.id)
                else
                    MarkItemFavorite(movie.id)
                end if
                movie.favorite = not movie.favorite
            else
                ' If there are no other button matches, check if this is a simple "OK" Dialog & Close if so
                dialog = msg.getRoSGNode()
                if dialog.id = "OKDialog"
                    dialog.unobserveField("buttonSelected")
                    dialog.close = true
                end if
            end if
        else if isNodeEvent(msg, "optionSelected")
            button = msg.getRoSGNode()
            group = sceneManager.callFunc("getActiveScene")
            if button.id = "goto_search" and isValid(group)
                ' Exit out of the side panel
                panel = group.findNode("options")
                panel.visible = false
                if isValid(group.lastFocus)
                    group.lastFocus.setFocus(true)
                else
                    group.setFocus(true)
                end if
                group = CreateSearchPage()
                sceneManager.callFunc("pushScene", group)
                group.findNode("SearchBox").findNode("search_Key").setFocus(true)
                group.findNode("SearchBox").findNode("search_Key").active = true
            else if button.id = "change_server"
                unset_setting("server")
                session.server.Delete()
                SignOut(false)
                sceneManager.callFunc("clearScenes")
                goto app_start
            else if button.id = "change_user"
                SignOut(false)
                sceneManager.callFunc("clearScenes")
                goto app_start
            else if button.id = "sign_out"
                SignOut()
                sceneManager.callFunc("clearScenes")
                goto app_start
            else if button.id = "settings"
                ' Exit out of the side panel
                panel = group.findNode("options")
                panel.visible = false
                if isValid(group) and isValid(group.lastFocus)
                    group.lastFocus.setFocus(true)
                else
                    group.setFocus(true)
                end if
                sceneManager.callFunc("settings")
            end if
        else if isNodeEvent(msg, "selectSubtitlePressed")
            node = m.scene.focusedChild
            if node.focusedChild <> invalid and node.focusedChild.isSubType("JFVideo")
                trackSelected = selectSubtitleTrack(node.Subtitles, node.SelectedSubtitle)
                if trackSelected <> invalid and trackSelected <> -2
                    changeSubtitleDuringPlayback(trackSelected)
                end if
            end if
        else if isNodeEvent(msg, "selectPlaybackInfoPressed")
            node = m.scene.focusedChild
            if node.focusedChild <> invalid and node.focusedChild.isSubType("JFVideo")
                info = GetPlaybackInfo()
                show_dialog(tr("Playback Information"), info)
            end if
        else if isNodeEvent(msg, "state")
            node = msg.getRoSGNode()
            if isValid(node) and isValid(node.state)
                if node.selectedItemType = "TvChannel" and node.state = "finished"
                    video = CreateVideoPlayerGroup(node.id)
                    m.global.sceneManager.callFunc("pushScene", video)
                    m.global.sceneManager.callFunc("deleteSceneAtIndex", 2)
                else if node.state = "finished"
                    node.control = "stop"

                    ' If node allows retrying using Transcode Url, give that shot
                    if isValid(node.retryWithTranscoding) and node.retryWithTranscoding
                        retryVideo = CreateVideoPlayerGroup(node.Id, invalid, node.audioIndex, true, false)
                        m.global.sceneManager.callFunc("popScene")
                        if isValid(retryVideo)
                            m.global.sceneManager.callFunc("pushScene", retryVideo)
                        end if
                    else if not isValid(node.showID)
                        sceneManager.callFunc("popScene")
                    else
                        autoPlayNextEpisode(node.id, node.showID)
                    end if
                end if
            end if
        else if type(msg) = "roDeviceInfoEvent"
            event = msg.GetInfo()

            if event.exitedScreensaver = true
                sceneManager.callFunc("resetTime")
                group = sceneManager.callFunc("getActiveScene")
                if isValid(group)
                    ' refresh the current view
                    if group.isSubType("JFScreen")
                        group.callFunc("OnScreenShown")
                    end if
                end if
            else if isValid(event.audioGuideEnabled)
                tmpGlobalDevice = m.global.device
                tmpGlobalDevice.AddReplace("isaudioguideenabled", event.audioGuideEnabled)

                ' update global device array
                m.global.setFields({ device: tmpGlobalDevice })
            else if isValid(event.Mode)
                ' Indicates the current global setting for the Caption Mode property, which may be one of the following values:
                ' "On"
                ' "Off"
                ' "Instant replay"
                ' "When mute" (Only returned for a TV; this option is not available on STBs).
                print "event.Mode = ", event.Mode
                if isValid(event.Mute)
                    print "event.Mute = ", event.Mute
                end if
            else if isValid(event.linkStatus)
                ' True if the device currently seems to have an active network connection.
                print "event.linkStatus = ", event.linkStatus
            else if isValid(event.generalMemoryLevel)
                ' This event will be sent first when the OS transitions from "normal" to "low" state and will continue to be sent while in "low" or "critical" states.
                '   - "normal" means that the general memory is within acceptable levels
                '   - "low" means that the general memory is below acceptable levels but not critical
                '   - "critical" means that general memory are at dangerously low level and that the OS may force terminate the application
                print "event.generalMemoryLevel = ", event.generalMemoryLevel
                session.Update("memoreyLevel", event.generalMemoryLevel)
            else if isValid(event.audioCodecCapabilityChanged)
                ' The audio codec capability has changed if true.
                print "event.audioCodecCapabilityChanged = ", event.audioCodecCapabilityChanged

                postTask = createObject("roSGNode", "PostTask")
                postTask.arrayData = getDeviceCapabilities()
                postTask.apiUrl = "/Sessions/Capabilities/Full"
                postTask.control = "RUN"
            else if isValid(event.videoCodecCapabilityChanged)
                ' The video codec capability has changed if true.
                print "event.videoCodecCapabilityChanged = ", event.videoCodecCapabilityChanged

                postTask = createObject("roSGNode", "PostTask")
                postTask.arrayData = getDeviceCapabilities()
                postTask.apiUrl = "/Sessions/Capabilities/Full"
                postTask.control = "RUN"
            else if isValid(event.appFocus)
                ' It is set to False when the System Overlay (such as the confirm partner button HUD or the caption control overlay) takes focus and True when the channel regains focus
                print "event.appFocus = ", event.appFocus
            else
                print "Unhandled roDeviceInfoEvent:"
                print msg.GetInfo()
            end if
        else if type(msg) = "roInputEvent"
            if msg.IsInput()
                info = msg.GetInfo()
                if info.DoesExist("mediatype") and info.DoesExist("contentid")
                    inputEventVideo = {
                        id: info.contentId,
                        type: "video"
                    }

                    m.global.queueManager.callFunc("clear")
                    m.global.queueManager.callFunc("push", inputEventVideo)
                    m.global.queueManager.callFunc("playQueue")
                end if
            end if
        else if isNodeEvent(msg, "dataReturned")
            popupNode = msg.getRoSGNode()
            stopLoadingSpinner()
            if isValid(popupNode) and isValid(popupNode.returnData)
                selectedItem = m.global.queueManager.callFunc("getHold")
                m.global.queueManager.callFunc("clearHold")

                if isValid(selectedItem) and selectedItem.count() > 0 and isValid(selectedItem[0])
                    if popupNode.returnData.indexselected = 0
                        'Resume video from resume point
                        startLoadingSpinner()
                        startingPoint = 0

                        if isValid(selectedItem[0].json) and isValid(selectedItem[0].json.UserData) and isValid(selectedItem[0].json.UserData.PlaybackPositionTicks)
                            if selectedItem[0].json.UserData.PlaybackPositionTicks > 0
                                startingPoint = selectedItem[0].json.UserData.PlaybackPositionTicks
                            end if
                        end if

                        selectedItem[0].startingPoint = startingPoint
                        m.global.queueManager.callFunc("clear")
                        m.global.queueManager.callFunc("push", selectedItem[0])
                        m.global.queueManager.callFunc("playQueue")
                    else if popupNode.returnData.indexselected = 1
                        'Start Over from beginning selected, set position to 0
                        startLoadingSpinner()
                        selectedItem[0].startingPoint = 0
                        m.global.queueManager.callFunc("clear")
                        m.global.queueManager.callFunc("push", selectedItem[0])
                        m.global.queueManager.callFunc("playQueue")
                    else if popupNode.returnData.indexselected = 2
                        ' User chose Go to series
                        CreateSeriesDetailsGroup(selectedItem[0].json.SeriesId)
                    else if popupNode.returnData.indexselected = 3
                        ' User chose Go to season
                        CreateSeasonDetailsGroupByID(selectedItem[0].json.SeriesId, selectedItem[0].json.seasonID)
                    else if popupNode.returnData.indexselected = 4
                        ' User chose Go to episode
                        CreateMovieDetailsGroup(selectedItem[0])
                    end if
                end if
            end if
        else
            print "Unhandled " type(msg)
            print msg
        end if
    end while

end sub