Merge branch 'master' into OSDInfo

This commit is contained in:
1hitsong 2024-02-22 20:45:42 -05:00 committed by GitHub
commit 1b863ba34c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
357 changed files with 3845 additions and 909 deletions

View File

@ -3,16 +3,13 @@ name: build-dev
on: on:
pull_request: pull_request:
push: push:
branches:
- master
- "*.*.z"
jobs: jobs:
dev: dev:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with: with:
node-version: "lts/*" node-version: "lts/*"
cache: "npm" cache: "npm"
@ -22,7 +19,7 @@ jobs:
run: npm run ropm run: npm run ropm
- name: Build app - name: Build app
run: npm run build run: npm run build
- uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4 - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
with: with:
name: Jellyfin-Roku-dev-${{ github.sha }} name: Jellyfin-Roku-dev-${{ github.sha }}
path: ${{ github.workspace }}/build/staging path: ${{ github.workspace }}/build/staging

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with: with:
node-version: "lts/*" node-version: "lts/*"
cache: "npm" cache: "npm"
@ -23,7 +23,7 @@ jobs:
run: npm run ropm run: npm run ropm
- name: Build app for production - name: Build app for production
run: npm run build-prod run: npm run build-prod
- uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4 - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
with: with:
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }} name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
path: ${{ github.workspace }}/build/staging path: ${{ github.workspace }}/build/staging

235
.github/workflows/bump-version.yml vendored Normal file
View File

@ -0,0 +1,235 @@
name: "Create PR to bump version"
on:
workflow_dispatch:
inputs:
targetBranch:
description: 'Target Branch'
required: true
type: choice
options:
- bugfix
- master
versionType:
description: 'What Version to Bump'
required: true
type: choice
options:
- build
- minor
- major
jobs:
build:
if: ${{ github.event.inputs.versionType == 'build' }}
runs-on: ubuntu-latest
steps:
# Setup
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Install required packages
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save targetBranch to env
if: github.event.inputs.targetBranch != 'bugfix'
run: echo "targetBranch=${{ github.event.inputs.targetBranch }}" >> $GITHUB_ENV
# Save old version
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
# Bugfix branch
- name: Save bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "bugfixBranch=${{ env.oldMajor }}.${{ env.oldMinor }}.z" >> $GITHUB_ENV
- name: Update targetBranch with actual bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
- name: Checkout bugfix branch
if: github.event.inputs.targetBranch == 'bugfix'
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: ${{ env.targetBranch }}
# Save old version again if needed
- name: Find and save old major_version from manifest
if: github.event.inputs.targetBranch == 'bugfix'
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
if: github.event.inputs.targetBranch == 'bugfix'
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
if: github.event.inputs.targetBranch == 'bugfix'
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
# Calculate new version
- name: Calculate new build_version
run: echo "newBuild=$((${{ env.oldBuild }} + 1))" >> $GITHUB_ENV
- name: Save new version to env var
run: echo "newVersion=${{ env.oldMajor }}.${{ env.oldMinor }}.${{ env.newBuild }}" >> $GITHUB_ENV
- name: Save a copy of newVersion without periods to env var
run: echo "newVersionSlug=${{ env.newVersion }}" | sed -e 's/\.//g' >> $GITHUB_ENV
# Update files with new versions
- name: Update manifest build_version
run: sed -i "s/build_version=.*/build_version=${{ env.newBuild }}/g" manifest
- name: Update package-lock.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package-lock.json version 2
run: echo "$( jq '.packages."".version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package.json )" > package.json
- name: Update Makefile version
run: sed -i "s/VERSION := .*/VERSION := ${{ env.newVersion }}/g" Makefile
# Create PR
- name: Save new branch name to env
run: echo "newBranch=bump-${{ github.event.inputs.targetBranch }}-to-${{ env.newVersionSlug }}" >> $GITHUB_ENV
- name: Create PR with new version
env:
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
run: |-
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout -b "${{ env.newBranch }}"
git add .
git commit -m "Bump ${{ github.event.inputs.versionType }} version"
git push --set-upstream origin "${{ env.newBranch }}"
gh pr create --title "Bump ${{ github.event.inputs.targetBranch }} branch to ${{ env.newVersion }}" --body "Bump version to prep for next release." --label ignore-changelog --base ${{ env.targetBranch }}
minor:
if: ${{ github.event.inputs.versionType == 'minor' }}
runs-on: ubuntu-latest
steps:
# Setup
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Install jq to update json
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save targetBranch to env
if: github.event.inputs.targetBranch != 'bugfix'
run: echo "targetBranch=${{ github.event.inputs.targetBranch }}" >> $GITHUB_ENV
# Save old version
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
# Bugfix branch
- name: Save bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "bugfixBranch=${{ env.oldMajor }}.${{ env.oldMinor }}.z" >> $GITHUB_ENV
- name: Update targetBranch with actual bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
- name: Checkout bugfix branch
if: github.event.inputs.targetBranch == 'bugfix'
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: ${{ env.targetBranch }}
# Calculate new version
- name: Calculate new build_version
run: echo "newMinor=$((${{ env.oldMinor }} + 1))" >> $GITHUB_ENV
- name: Save new version to env var
run: echo "newVersion=${{ env.oldMajor }}.${{ env.newMinor }}.0" >> $GITHUB_ENV
- name: Save a copy of newVersion without periods to env var
run: echo "newVersionSlug=${{ env.newVersion }}" | sed -e 's/\.//g' >> $GITHUB_ENV
# Update files with new versions
- name: Update manifest minor_version
run: sed -i "s/minor_version=.*/minor_version=${{ env.newMinor }}/g" manifest
- name: Update manifest build_version
run: sed -i "s/build_version=.*/build_version=0/g" manifest
- name: Update package-lock.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package-lock.json version 2
run: echo "$( jq '.packages."".version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package.json )" > package.json
- name: Update Makefile version
run: sed -i "s/VERSION := .*/VERSION := ${{ env.newVersion }}/g" Makefile
# Create PR
- name: Save new branch name to env
run: echo "newBranch=bump-${{ github.event.inputs.targetBranch }}-to-${{ env.newVersionSlug }}" >> $GITHUB_ENV
- name: Create PR with new version
env:
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
run: |-
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout -b "${{ env.newBranch }}"
git add .
git commit -m "Bump ${{ github.event.inputs.versionType }} version"
git push --set-upstream origin "${{ env.newBranch }}"
gh pr create --title "Bump ${{ github.event.inputs.targetBranch }} branch to ${{ env.newVersion }}" --body "Bump version to prep for next release." --label ignore-changelog --base ${{ env.targetBranch }}
major:
if: ${{ github.event.inputs.versionType == 'major' }}
runs-on: ubuntu-latest
steps:
# Setup
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- name: Install jq to update json
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: jq
- name: Save targetBranch to env
if: github.event.inputs.targetBranch != 'bugfix'
run: echo "targetBranch=${{ github.event.inputs.targetBranch }}" >> $GITHUB_ENV
# Save old version
- name: Find and save old major_version from manifest
run: awk 'BEGIN { FS="=" } /^major_version/ { print "oldMajor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old minor_version from manifest
run: awk 'BEGIN { FS="=" } /^minor_version/ { print "oldMinor="$2; }' manifest >> $GITHUB_ENV
- name: Find and save old build_version from manifest
run: awk 'BEGIN { FS="=" } /^build_version/ { print "oldBuild="$2; }' manifest >> $GITHUB_ENV
# Bugfix branch
- name: Save bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "bugfixBranch=${{ env.oldMajor }}.${{ env.oldMinor }}.z" >> $GITHUB_ENV
- name: Update targetBranch with actual bugfix branch name
if: github.event.inputs.targetBranch == 'bugfix'
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
- name: Checkout bugfix branch
if: github.event.inputs.targetBranch == 'bugfix'
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
ref: ${{ env.targetBranch }}
# Calculate new version
- name: Calculate new build_version
run: echo "newMajor=$((${{ env.oldMajor }} + 1))" >> $GITHUB_ENV
- name: Save new version to env var
run: echo "newVersion=${{ env.newMajor }}.0.0" >> $GITHUB_ENV
- name: Save a copy of newVersion without periods to env var
run: echo "newVersionSlug=${{ env.newVersion }}" | sed -e 's/\.//g' >> $GITHUB_ENV
# Update files with new versions
- name: Update manifest major_version
run: sed -i "s/major_version=.*/major_version=${{ env.newMajor }}/g" manifest
- name: Update manifest minor_version
run: sed -i "s/minor_version=.*/minor_version=0/g" manifest
- name: Update manifest build_version
run: sed -i "s/build_version=.*/build_version=0/g" manifest
- name: Update package-lock.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package-lock.json version 2
run: echo "$( jq '.packages."".version = "'"${{ env.newVersion }}"'"' package-lock.json )" > package-lock.json
- name: Update package.json version
run: echo "$( jq '.version = "'"${{ env.newVersion }}"'"' package.json )" > package.json
- name: Update Makefile version
run: sed -i "s/VERSION := .*/VERSION := ${{ env.newVersion }}/g" Makefile
# Create PR
- name: Save new branch name to env
run: echo "newBranch=bump-${{ github.event.inputs.targetBranch }}-to-${{ env.newVersionSlug }}" >> $GITHUB_ENV
- name: Create PR with new version
env:
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
run: |-
git config user.name "jellyfin-bot"
git config user.email "team@jellyfin.org"
git checkout -b "${{ env.newBranch }}"
git add .
git commit -m "Bump ${{ github.event.inputs.versionType }} version"
git push --set-upstream origin "${{ env.newBranch }}"
gh pr create --title "Bump ${{ github.event.inputs.targetBranch }} branch to ${{ env.newVersion }}" --body "Bump version to prep for next release." --label ignore-changelog --base ${{ env.targetBranch }}

View File

@ -34,10 +34,10 @@ jobs:
- name: Setup Pages - name: Setup Pages
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4 uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@0252fc4ba7626f0298f0cf00902a25c6afc77fa8 # v3 uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with: with:
# Only upload the api docs folder # Only upload the api docs folder
path: "docs/api" path: "docs/api"
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@87c3283f01cd6fe19a0ab93a23b2f6fcba5a8e42 # v4 uses: actions/deploy-pages@decdde0ac072f6dcbe43649d82d9c635fff5b4e4 # v4

View File

@ -63,7 +63,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with: with:
node-version: "lts/*" node-version: "lts/*"
cache: "npm" cache: "npm"
@ -73,7 +73,7 @@ jobs:
run: npm run ropm run: npm run ropm
- name: Build app for production - name: Build app for production
run: npm run build-prod run: npm run build-prod
- uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4 - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
with: with:
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }} name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
path: ${{ github.workspace }}/build/staging path: ${{ github.workspace }}/build/staging

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4 - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
with: with:
node-version: "lts/*" node-version: "lts/*"
cache: "npm" cache: "npm"

View File

@ -3,7 +3,7 @@
# If you want to get_images, you'll also need convert from ImageMagick # If you want to get_images, you'll also need convert from ImageMagick
########################################################################## ##########################################################################
VERSION := 2.0.2 VERSION := 2.0.5
## usage ## usage

View File

@ -31,9 +31,9 @@
], ],
"diagnosticFilters": ["node_modules/**/*", "**/roku_modules/**/*"], "diagnosticFilters": ["node_modules/**/*", "**/roku_modules/**/*"],
"autoImportComponentScript": true, "autoImportComponentScript": true,
"allowBrighterScriptInBrightScript": true,
"createPackage": false, "createPackage": false,
"stagingFolderPath": "build", "stagingFolderPath": "build",
"retainStagingDir": true,
"plugins": ["rooibos-roku"], "plugins": ["rooibos-roku"],
"rooibos": { "rooibos": {
"isRecordingCodeCoverage": false, "isRecordingCodeCoverage": false,

View File

@ -18,12 +18,13 @@
{ {
"src": "settings/**/*", "src": "settings/**/*",
"dest": "settings" "dest": "settings"
} },
"manifest"
], ],
"diagnosticFilters": ["node_modules/**/*", "**/roku_modules/**/*"], "diagnosticFilters": ["node_modules/**/*", "**/roku_modules/**/*"],
"autoImportComponentScript": true, "autoImportComponentScript": true,
"allowBrighterScriptInBrightScript": true,
"stagingFolderPath": "build", "stagingFolderPath": "build",
"retainStagingDir": true,
"plugins": ["rooibos-roku"], "plugins": ["rooibos-roku"],
"rooibos": { "rooibos": {
"isRecordingCodeCoverage": false, "isRecordingCodeCoverage": false,

View File

@ -48,7 +48,6 @@
</children> </children>
<interface> <interface>
<field id="selectedItem" type="node" alwaysNotify="true" /> <field id="selectedItem" type="node" alwaysNotify="true" />
<field id="focusedChild" type="node" onChange="focusChanged" />
<field id="itemAlphaSelected" type="string" /> <field id="itemAlphaSelected" type="string" />
</interface> </interface>
</component> </component>

View File

@ -38,10 +38,13 @@ sub init()
end sub end sub
sub itemContentChanged() sub itemContentChanged()
m.backdrop.blendColor = "#00a4db" ' set default in case global var is invalid
localGlobal = m.global
' Set Random background colors from pallet if isValid(localGlobal) and isValid(localGlobal.constants) and isValid(localGlobal.constants.poster_bg_pallet)
posterBackgrounds = m.global.constants.poster_bg_pallet posterBackgrounds = localGlobal.constants.poster_bg_pallet
m.backdrop.blendColor = posterBackgrounds[rnd(posterBackgrounds.count()) - 1] m.backdrop.blendColor = posterBackgrounds[rnd(posterBackgrounds.count()) - 1]
end if
itemData = m.top.itemContent itemData = m.top.itemContent
@ -56,7 +59,8 @@ sub itemContentChanged()
m.itemIcon.uri = itemData.iconUrl m.itemIcon.uri = itemData.iconUrl
m.itemText.text = itemData.Title m.itemText.text = itemData.Title
else if itemData.type = "Series" else if itemData.type = "Series"
if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings)
if localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount) if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
if itemData.json.UserData.UnplayedItemCount > 0 if itemData.json.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true m.unplayedCount.visible = true
@ -67,6 +71,7 @@ sub itemContentChanged()
end if end if
end if end if
end if end if
end if
if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played = true if isValid(itemData.json) and isValid(itemData.json.UserData) and isValid(itemData.json.UserData.Played) and itemData.json.UserData.Played = true
m.playedIndicator.visible = true m.playedIndicator.visible = true
end if end if

View File

@ -164,6 +164,8 @@ sub loadItems()
tmp.image = PosterImage(item.id, { "maxHeight": 425, "maxWidth": 290, "quality": "90" }) tmp.image = PosterImage(item.id, { "maxHeight": 425, "maxWidth": 290, "quality": "90" })
else if item.type = "Episode" else if item.type = "Episode"
tmp = CreateObject("roSGNode", "TVEpisode") tmp = CreateObject("roSGNode", "TVEpisode")
else if LCase(item.Type) = "recording"
tmp = CreateObject("roSGNode", "RecordingData")
else if item.Type = "Genre" else if item.Type = "Genre"
tmp = CreateObject("roSGNode", "ContentNode") tmp = CreateObject("roSGNode", "ContentNode")
tmp.title = item.name tmp.title = item.name

View File

@ -82,8 +82,8 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
videotype = LCase(meta.type) videotype = LCase(meta.type)
' Check for any Live TV streams coming from other places other than the TV Guide ' Check for any Live TV streams or Recordings coming from other places other than the TV Guide
if isValid(meta.json) and isValid(meta.json.ChannelId) if videotype = "recording" or (isValid(meta.json) and isValid(meta.json.ChannelId))
if isValid(meta.json.EpisodeTitle) if isValid(meta.json.EpisodeTitle)
meta.title = meta.json.EpisodeTitle meta.title = meta.json.EpisodeTitle
else if isValid(meta.json.Name) else if isValid(meta.json.Name)
@ -263,7 +263,14 @@ function defaultSubtitleTrackFromVid(videoID) as integer
if not isValidAndNotEmpty(meta.json.MediaSources[0].MediaStreams) then return SubtitleSelection.none if not isValidAndNotEmpty(meta.json.MediaSources[0].MediaStreams) then return SubtitleSelection.none
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams) subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
selectedAudioLanguage = meta.json.MediaSources[0].MediaStreams[m.top.selectedAudioStreamIndex].Language ?? ""
selectedAudioLanguage = ""
audioMediaStream = meta.json.MediaSources[0].MediaStreams[m.top.selectedAudioStreamIndex]
' Ensure audio media stream is valid before using language property
if isValid(audioMediaStream)
selectedAudioLanguage = audioMediaStream.Language ?? ""
end if
defaultTextSubs = defaultSubtitleTrack(subtitles["text"], selectedAudioLanguage, true) ' Find correct subtitle track (forced text) defaultTextSubs = defaultSubtitleTrack(subtitles["text"], selectedAudioLanguage, true) ' Find correct subtitle track (forced text)
if defaultTextSubs <> SubtitleSelection.none if defaultTextSubs <> SubtitleSelection.none

View File

@ -2,9 +2,10 @@ import "pkg:/source/utils/config.bs"
sub init() sub init()
m.top.id = "overhang" m.top.id = "overhang"
' hide seperators till they're needed m.top.translation = [54, 0]
m.leftSeperator = m.top.findNode("overlayLeftSeperator")
m.leftSeperator.visible = "false" m.leftGroup = m.top.findNode("overlayLeftGroup")
m.rightGroup = m.top.findNode("overlayRightGroup")
m.rightSeperator = m.top.findNode("overlayRightSeperator") m.rightSeperator = m.top.findNode("overlayRightSeperator")
' set font sizes ' set font sizes
m.optionText = m.top.findNode("overlayOptionsText") m.optionText = m.top.findNode("overlayOptionsText")
@ -38,7 +39,7 @@ end sub
sub onVisibleChange() sub onVisibleChange()
if m.top.disableMoveAnimation if m.top.disableMoveAnimation
m.top.translation = [0, 0] m.top.translation = [54, 0]
return return
end if end if
if m.top.isVisible if m.top.isVisible
@ -50,16 +51,7 @@ sub onVisibleChange()
end sub end sub
sub updateTitle() sub updateTitle()
if m.top.title <> ""
m.leftSeperator.visible = "true"
else
m.leftSeperator.visible = "false"
end if
m.title.text = m.top.title m.title.text = m.top.title
if not m.hideClock
resetTime()
end if
end sub end sub
sub setClockVisibility() sub setClockVisibility()
@ -84,7 +76,9 @@ end sub
sub updateUser() sub updateUser()
setRightSeperatorVisibility() setRightSeperatorVisibility()
user = m.top.findNode("overlayCurrentUser") user = m.top.findNode("overlayCurrentUser")
if isValid(user)
user.text = m.top.currentUser user.text = m.top.currentUser
end if
end sub end sub
sub updateTime() sub updateTime()
@ -145,3 +139,23 @@ sub updateOptions()
m.optionStar.visible = false m.optionStar.visible = false
end if end if
end sub end sub
' component boolean field isLogoVisibleChange has changed value
sub isLogoVisibleChange()
isLogoVisible = m.top.isLogoVisible
scene = m.top.getScene()
logo = scene.findNode("overlayLogo")
if isLogoVisible
if not isValid(logo)
posterLogo = createLogoPoster()
m.leftGroup.insertChild(posterLogo, 0)
end if
else
' remove the logo
if isValid(logo)
m.leftGroup.removeChild(logo)
end if
end if
end sub

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<component name="JFOverhang" extends="Group"> <component name="JFOverhang" extends="Group">
<children> <children>
<Poster id="overlayLogo" uri="pkg:/images/logo.png" translation="[70, 53]" width="270" height="72" /> <LayoutGroup id="overlayLeftGroup" layoutDirection="horiz" translation="[54, 54]" itemSpacings="60">
<LayoutGroup id="overlayLeftGroup" layoutDirection="horiz" translation="[375, 53]" itemSpacings="30"> <Poster id="overlayLogo" uri="pkg:/images/logo.png" height="66" width="191" />
<Rectangle id="overlayLeftSeperator" color="#666666" width="2" height="64" />
<ScrollingLabel id="overlayTitle" font="font:LargeSystemFont" vertAlign="center" height="64" maxWidth="1100" repeatCount="0" /> <ScrollingLabel id="overlayTitle" font="font:LargeSystemFont" vertAlign="center" height="64" maxWidth="1100" repeatCount="0" />
</LayoutGroup> </LayoutGroup>
<LayoutGroup id="overlayRightGroup" layoutDirection="horiz" itemSpacings="30" translation="[1820, 53]" horizAlignment="right"> <LayoutGroup id="overlayRightGroup" layoutDirection="horiz" itemSpacings="30" translation="[1766, 53]" horizAlignment="right">
<Label id="overlayCurrentUser" font="font:MediumSystemFont" width="300" horizAlign="right" vertAlign="center" height="64" /> <Label id="overlayCurrentUser" font="font:MediumSystemFont" width="300" horizAlign="right" vertAlign="center" height="64" />
<Rectangle id="overlayRightSeperator" color="#666666" width="2" height="64" visible="false" /> <Rectangle id="overlayRightSeperator" color="#666666" width="2" height="64" visible="false" />
<LayoutGroup id="overlayTimeGroup" layoutDirection="horiz" horizAlignment="right" itemSpacings="0"> <LayoutGroup id="overlayTimeGroup" layoutDirection="horiz" horizAlignment="right" itemSpacings="0">
@ -17,7 +16,7 @@
</LayoutGroup> </LayoutGroup>
</LayoutGroup> </LayoutGroup>
<LayoutGroup layoutDirection="horiz" horizAlignment="right" translation="[1820, 125]" vertAlignment="custom"> <LayoutGroup layoutDirection="horiz" horizAlignment="right" translation="[1766, 125]" vertAlignment="custom">
<Label id="overlayOptionsStar" font="font:LargeSystemFont" text="*" /> <Label id="overlayOptionsStar" font="font:LargeSystemFont" text="*" />
<Label id="overlayOptionsText" font="font:SmallSystemFont" text="Options" translation="[0,6]" /> <Label id="overlayOptionsText" font="font:SmallSystemFont" text="Options" translation="[0,6]" />
</LayoutGroup> </LayoutGroup>
@ -38,6 +37,6 @@
<field id="showOptions" value="true" type="boolean" onChange="updateOptions" /> <field id="showOptions" value="true" type="boolean" onChange="updateOptions" />
<field id="isVisible" value="true" type="boolean" onChange="onVisibleChange" /> <field id="isVisible" value="true" type="boolean" onChange="onVisibleChange" />
<field id="disableMoveAnimation" value="false" type="boolean" /> <field id="disableMoveAnimation" value="false" type="boolean" />
<function name="resetTime" /> <field id="isLogoVisible" value="true" type="boolean" onChange="isLogoVisibleChange" />
</interface> </interface>
</component> </component>

View File

@ -1,8 +1,20 @@
import "pkg:/source/utils/misc.bs"
sub init() sub init()
m.top.setFocus(true) m.top.setFocus(true)
m.top.optionsAvailable = false m.top.optionsAvailable = false
end sub end sub
' JFScreen hook.
sub OnScreenShown()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = true
overhang.currentUser = ""
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean
' Returns true if user navigates to a new focusable element ' Returns true if user navigates to a new focusable element
if not press then return false if not press then return false

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<component name="LoginScene" extends="JFGroup"> <component name="LoginScene" extends="JFScreen">
<children> <children>
<label text="Enter Configuration" <label text="Enter Configuration"
id="prompt" id="prompt"

View File

@ -156,3 +156,14 @@ end function
sub clearErrorMessage() sub clearErrorMessage()
m.top.errorMessage = "" m.top.errorMessage = ""
end sub end sub
' JFScreen hook called when the screen is displayed by the screen manager
sub OnScreenShown()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = true
overhang.currentUser = ""
overhang.title = ""
end if
end sub

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<component name="SetServerScreen" extends="JFGroup"> <component name="SetServerScreen" extends="JFScreen">
<interface> <interface>
<field id="serverUrl" type="string" alias="serverUrlTextbox.text" /> <field id="serverUrl" type="string" alias="serverUrlTextbox.text" />
<field id="serverWidth" alias="serverUrlOutline.width,serverUrlTextbox.width,serverUrlContainer.width,submitSizer.width" value="1620" /> <field id="serverWidth" alias="serverUrlOutline.width,serverUrlTextbox.width,serverUrlContainer.width,submitSizer.width" value="1620" />

View File

@ -31,7 +31,7 @@ sub setData()
m.top.iconUrl = "pkg:/images/media_type_icons/folder_white.png" m.top.iconUrl = "pkg:/images/media_type_icons/folder_white.png"
end if end if
else if datum.type = "Episode" or datum.type = "MusicVideo" else if datum.type = "Episode" or LCase(datum.type) = "recording" or datum.type = "MusicVideo"
m.top.isWatched = datum.UserData.Played m.top.isWatched = datum.UserData.Played
imgParams = {} imgParams = {}

View File

@ -0,0 +1,5 @@
' Called whenever m.top.json changes.
' It is expected that each node that extends JFContentItem will override this function
sub setFields()
end sub

View File

@ -0,0 +1,20 @@
import "pkg:/source/utils/misc.bs"
sub setFields()
datum = m.top.json
m.top.id = datum.id
m.top.title = datum.name
m.top.showID = datum.SeriesID
m.top.seasonID = datum.SeasonID
m.top.overview = datum.overview
m.top.favorite = datum.UserData.isFavorite
end sub
sub setPoster()
if isValid(m.top.image)
m.top.posterURL = m.top.image.url
else
m.top.posterURL = ""
end if
end sub

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<component name="RecordingData" extends="ContentNode">
<interface>
<field id="id" type="string" />
<field id="title" type="string" />
<field id="image" type="node" onChange="setPoster" />
<field id="posterURL" type="string" />
<field id="showID" type="string" />
<field id="seasonID" type="string" />
<field id="overview" type="string" />
<field id="type" type="string" value="Recording" />
<field id="startingPoint" type="longinteger" value="0" />
<field id="json" type="assocarray" onChange="setFields" />
<field id="selectedVideoStreamId" type="string" />
<field id="selectedAudioStreamIndex" type="integer" />
<field id="favorite" type="boolean" />
</interface>
</component>

View File

@ -124,20 +124,18 @@ sub popScene()
stopLoadingSpinner() stopLoadingSpinner()
end sub end sub
' '
' Return group at top of stack without removing ' Return group at top of stack without removing
function getActiveScene() as object function getActiveScene() as object
return m.groups.peek() return m.groups.peek()
end function end function
' '
' Clear all content from group stack ' Clear all content from group stack
sub clearScenes() sub clearScenes()
if m.content <> invalid then m.content.removeChildrenIndex(m.content.getChildCount(), 0) if m.content <> invalid then m.content.removeChildrenIndex(m.content.getChildCount(), 0)
for each group in m.groups for each group in m.groups
if LCase(group.subtype()) = "jfscreen" if type(group) = "roSGNode" and group.isSubtype("JFScreen")
group.callFunc("OnScreenHidden") group.callFunc("OnScreenHidden")
end if end if
end for end for
@ -191,35 +189,30 @@ sub registerOverhangData(group)
end if end if
end sub end sub
' '
' Remove observers for overhang data ' Remove observers for overhang data
sub unregisterOverhangData(group) sub unregisterOverhangData(group)
group.unobserveField("overhangTitle") group.unobserveField("overhangTitle")
end sub end sub
' '
' Update overhang title ' Update overhang title
sub updateOverhangTitle(msg) sub updateOverhangTitle(msg)
m.overhang.title = msg.getData() m.overhang.title = msg.getData()
end sub end sub
' '
' Update options availability ' Update options availability
sub updateOptions(msg) sub updateOptions(msg)
m.overhang.showOptions = msg.getData() m.overhang.showOptions = msg.getData()
end sub end sub
' '
' Update whether the overhang is visible or not ' Update whether the overhang is visible or not
sub updateOverhangVisible(msg) sub updateOverhangVisible(msg)
m.overhang.visible = msg.getData() m.overhang.visible = msg.getData()
end sub end sub
' '
' Update username in overhang ' Update username in overhang
sub updateUser() sub updateUser()
@ -227,7 +220,6 @@ sub updateUser()
if m.overhang <> invalid then m.overhang.currentUser = m.top.currentUser if m.overhang <> invalid then m.overhang.currentUser = m.top.currentUser
end sub end sub
' '
' Reset time ' Reset time
sub resetTime() sub resetTime()

View File

@ -30,9 +30,16 @@ sub loadLibraries()
m.fadeInFocusBitmap.control = "start" m.fadeInFocusBitmap.control = "start"
end sub end sub
' JFScreen hook that gets ran as needed. ' JFScreen hook called when the screen is displayed by the screen manager
' Used to update the focus, the state of the data, and tells the server about the device profile
sub OnScreenShown() sub OnScreenShown()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = true
overhang.currentUser = m.global.session.user.name
overhang.title = tr("Home")
end if
if isValid(m.top.lastFocus) if isValid(m.top.lastFocus)
m.top.lastFocus.setFocus(true) m.top.lastFocus.setFocus(true)
else else
@ -53,6 +60,17 @@ sub OnScreenShown()
end if end if
end sub end sub
' JFScreen hook called when the screen is hidden by the screen manager
sub OnScreenHidden()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = false
overhang.currentUser = ""
overhang.title = ""
end if
end sub
' Triggered by m.postTask after completing a post. ' Triggered by m.postTask after completing a post.
' Empty the task data when finished. ' Empty the task data when finished.
sub postFinished() sub postFinished()

View File

@ -36,9 +36,10 @@ end sub
sub itemContentChanged() sub itemContentChanged()
m.unplayedCount.visible = false if isValid(m.unplayedCount) then m.unplayedCount.visible = false
itemData = m.top.itemContent itemData = m.top.itemContent
if itemData = invalid then return if itemData = invalid then return
localGlobal = m.global
itemData.Title = itemData.name ' Temporarily required while we move from "HomeItem" to "JFContentItem" itemData.Title = itemData.name ' Temporarily required while we move from "HomeItem" to "JFContentItem"
@ -56,21 +57,22 @@ sub itemContentChanged()
if itemData.isWatched if itemData.isWatched
m.playedIndicator.visible = true m.playedIndicator.visible = true
m.unplayedCount.visible = false
else else
m.playedIndicator.visible = false m.playedIndicator.visible = false
if LCase(itemData.type) = "series" if LCase(itemData.type) = "series"
if m.global.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] = false if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings)
if not localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"]
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount) if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
if itemData.json.UserData.UnplayedItemCount > 0 if itemData.json.UserData.UnplayedItemCount > 0
m.unplayedCount.visible = true if isValid(m.unplayedCount) then m.unplayedCount.visible = true
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
end if end if
end if end if
end if end if
end if end if
end if end if
end if
' Format the Data based on the type of Home Data ' Format the Data based on the type of Home Data
if itemData.type = "CollectionFolder" or itemData.type = "UserView" or itemData.type = "Channel" if itemData.type = "CollectionFolder" or itemData.type = "UserView" or itemData.type = "Channel"
@ -121,7 +123,7 @@ sub itemContentChanged()
return return
end if end if
if itemData.type = "Episode" if itemData.type = "Episode" or LCase(itemData.type) = "recording"
m.itemText.text = itemData.json.SeriesName m.itemText.text = itemData.json.SeriesName
if itemData.PlayedPercentage > 0 if itemData.PlayedPercentage > 0

View File

@ -75,12 +75,17 @@ sub processUserSections()
m.expectedRowCount = 1 ' the favorites row is hardcoded to always show atm m.expectedRowCount = 1 ' the favorites row is hardcoded to always show atm
m.processedRowCount = 0 m.processedRowCount = 0
sessionUser = m.global.session.user
' calculate expected row count by processing homesections ' calculate expected row count by processing homesections
for i = 0 to 6 for i = 0 to 6
sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()]) userSection = sessionUser.settings["homesection" + i.toStr()]
sectionName = userSection ?? "none"
sectionName = LCase(sectionName)
if sectionName = "latestmedia" if sectionName = "latestmedia"
' expect 1 row per filtered media library ' expect 1 row per filtered media library
m.filteredLatest = filterNodeArray(m.libraryData, "id", m.global.session.user.configuration.LatestItemsExcludes) m.filteredLatest = filterNodeArray(m.libraryData, "id", sessionUser.configuration.LatestItemsExcludes)
for each latestLibrary in m.filteredLatest for each latestLibrary in m.filteredLatest
if latestLibrary.collectionType <> "boxsets" and latestLibrary.collectionType <> "livetv" and latestLibrary.json.CollectionType <> "Program" if latestLibrary.collectionType <> "boxsets" and latestLibrary.collectionType <> "livetv" and latestLibrary.json.CollectionType <> "Program"
m.expectedRowCount++ m.expectedRowCount++
@ -94,7 +99,10 @@ sub processUserSections()
' Add home sections in order based on user settings ' Add home sections in order based on user settings
loadedSections = 0 loadedSections = 0
for i = 0 to 6 for i = 0 to 6
sectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()]) userSection = sessionUser.settings["homesection" + i.toStr()]
sectionName = userSection ?? "none"
sectionName = LCase(sectionName)
sectionLoaded = false sectionLoaded = false
if sectionName <> "none" if sectionName <> "none"
sectionLoaded = addHomeSection(sectionName) sectionLoaded = addHomeSection(sectionName)
@ -141,8 +149,13 @@ function getOriginalSectionIndex(sectionName as string) as integer
sectionIndex = 0 sectionIndex = 0
indexLatestMediaSection = 0 indexLatestMediaSection = 0
sessionUser = m.global.session.user
for i = 0 to 6 for i = 0 to 6
settingSectionName = LCase(m.global.session.user.settings["homesection" + i.toStr()]) userSection = sessionUser.settings["homesection" + i.toStr()]
settingSectionName = userSection ?? "none"
settingSectionName = LCase(settingSectionName)
if settingSectionName = "latestmedia" if settingSectionName = "latestmedia"
indexLatestMediaSection = i indexLatestMediaSection = i
end if end if

View File

@ -24,6 +24,16 @@ sub redraw()
m.top.findNode("UserRow").translation = [leftBorder, topBorder] m.top.findNode("UserRow").translation = [leftBorder, topBorder]
end sub end sub
' JFScreen hook called when the screen is displayed by the screen manager
sub OnScreenShown()
scene = m.top.getScene()
overhang = scene.findNode("overhang")
if isValid(overhang)
overhang.isLogoVisible = true
overhang.currentUser = ""
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false if not press then return false

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<component name="UserSelect" extends="JFGroup"> <component name="UserSelect" extends="JFScreen">
<children> <children>
<Label text="Please sign in" horizAlign="center" font="font:LargeSystemFont" height="100" width="1920" translation="[0, 200]" /> <Label text="Please sign in" horizAlign="center" font="font:LargeSystemFont" height="100" width="1920" translation="[0, 200]" />
<UserRow id="userRow" translation="[130, 360]" /> <UserRow id="userRow" translation="[130, 360]" />

View File

@ -125,6 +125,11 @@ sub playQueue()
return return
end if end if
if nextItemMediaType = "audiobook"
CreateAudioPlayerView()
return
end if
if nextItemMediaType = "musicvideo" if nextItemMediaType = "musicvideo"
CreateVideoPlayerView() CreateVideoPlayerView()
return return
@ -145,6 +150,11 @@ sub playQueue()
return return
end if end if
if nextItemMediaType = "recording"
CreateVideoPlayerView()
return
end if
if nextItemMediaType = "trailer" if nextItemMediaType = "trailer"
CreateVideoPlayerView() CreateVideoPlayerView()
return return
@ -249,6 +259,10 @@ sub setTopStartingPoint(positionTicks)
m.queue[0].startingPoint = positionTicks m.queue[0].startingPoint = positionTicks
end sub end sub
' getItemType: Returns the media type of the passed item
'
' @param {dynamic} item - Item to evaluate
' @return {string} indicating type of media item is
function getItemType(item) as string function getItemType(item) as string
if isValid(item) and isValid(item.json) and isValid(item.json.mediatype) and item.json.mediatype <> "" if isValid(item) and isValid(item.json) and isValid(item.json.mediatype) and item.json.mediatype <> ""
return LCase(item.json.mediatype) return LCase(item.json.mediatype)

View File

@ -9,6 +9,7 @@
<function name="getHold" /> <function name="getHold" />
<function name="getIsShuffled" /> <function name="getIsShuffled" />
<function name="getItemByIndex" /> <function name="getItemByIndex" />
<function name="getItemType" />
<function name="getPosition" /> <function name="getPosition" />
<function name="getQueue" /> <function name="getQueue" />
<function name="getQueueTypes" /> <function name="getQueueTypes" />

View File

@ -5,6 +5,11 @@ import "pkg:/source/utils/config.bs"
sub init() sub init()
m.top.optionsAvailable = false m.top.optionsAvailable = false
m.inScrubMode = false
m.lastRecordedPositionTimestamp = 0
m.scrubTimestamp = -1
m.playlistTypeCount = m.global.queueManager.callFunc("getQueueUniqueTypes").count()
setupAudioNode() setupAudioNode()
setupAnimationTasks() setupAnimationTasks()
@ -13,9 +18,8 @@ sub init()
setupDataTasks() setupDataTasks()
setupScreenSaver() setupScreenSaver()
m.playlistTypeCount = m.global.queueManager.callFunc("getQueueUniqueTypes").count()
m.buttonCount = m.buttons.getChildCount() m.buttonCount = m.buttons.getChildCount()
m.seekPosition.translation = [720 - (m.seekPosition.width / 2), m.seekPosition.translation[1]]
m.screenSaverTimeout = 300 m.screenSaverTimeout = 300
@ -32,6 +36,8 @@ sub init()
pageContentChanged() pageContentChanged()
setShuffleIconState() setShuffleIconState()
setLoopButtonImage() setLoopButtonImage()
m.buttons.setFocus(true)
end sub end sub
sub onScreensaverTimeoutLoaded() sub onScreensaverTimeoutLoaded()
@ -96,6 +102,20 @@ end sub
sub setupButtons() sub setupButtons()
m.buttons = m.top.findNode("buttons") m.buttons = m.top.findNode("buttons")
m.top.observeField("selectedButtonIndex", "onButtonSelectedChange") m.top.observeField("selectedButtonIndex", "onButtonSelectedChange")
' If we're playing a mixed playlist, remove the shuffle and loop buttons
if m.playlistTypeCount > 1
shuffleButton = m.top.findNode("shuffle")
m.buttons.removeChild(shuffleButton)
loopButton = m.top.findNode("loop")
m.buttons.removeChild(loopButton)
m.previouslySelectedButtonIndex = 0
m.top.selectedButtonIndex = 1
return
end if
m.previouslySelectedButtonIndex = 1 m.previouslySelectedButtonIndex = 1
m.top.selectedButtonIndex = 2 m.top.selectedButtonIndex = 2
end sub end sub
@ -117,13 +137,18 @@ sub setupInfoNodes()
m.playPosition = m.top.findNode("playPosition") m.playPosition = m.top.findNode("playPosition")
m.bufferPosition = m.top.findNode("bufferPosition") m.bufferPosition = m.top.findNode("bufferPosition")
m.seekBar = m.top.findNode("seekBar") m.seekBar = m.top.findNode("seekBar")
m.thumb = m.top.findNode("thumb")
m.shuffleIndicator = m.top.findNode("shuffleIndicator") m.shuffleIndicator = m.top.findNode("shuffleIndicator")
m.loopIndicator = m.top.findNode("loopIndicator") m.loopIndicator = m.top.findNode("loopIndicator")
m.positionTimestamp = m.top.findNode("positionTimestamp") m.positionTimestamp = m.top.findNode("positionTimestamp")
m.seekPosition = m.top.findNode("seekPosition")
m.seekTimestamp = m.top.findNode("seekTimestamp")
m.totalLengthTimestamp = m.top.findNode("totalLengthTimestamp") m.totalLengthTimestamp = m.top.findNode("totalLengthTimestamp")
end sub end sub
sub bufferPositionChanged() sub bufferPositionChanged()
if m.inScrubMode then return
if not isValid(m.global.audioPlayer.bufferingStatus) if not isValid(m.global.audioPlayer.bufferingStatus)
bufferPositionBarWidth = m.seekBar.width bufferPositionBarWidth = m.seekBar.width
else else
@ -141,6 +166,8 @@ sub bufferPositionChanged()
end sub end sub
sub audioPositionChanged() sub audioPositionChanged()
stopLoadingSpinner()
if m.global.audioPlayer.position = 0 if m.global.audioPlayer.position = 0
m.playPosition.width = 0 m.playPosition.width = 0
end if end if
@ -159,14 +186,22 @@ sub audioPositionChanged()
playPositionBarWidth = m.seekBar.width playPositionBarWidth = m.seekBar.width
end if end if
if not m.inScrubMode
moveSeekbarThumb(playPositionBarWidth)
' Change the seek position timestamp
m.seekTimestamp.text = secondsToHuman(m.global.audioPlayer.position, false)
end if
' Use animation to make the display smooth ' Use animation to make the display smooth
m.playPositionAnimationWidth.keyValue = [m.playPosition.width, playPositionBarWidth] m.playPositionAnimationWidth.keyValue = [m.playPosition.width, playPositionBarWidth]
m.playPositionAnimation.control = "start" m.playPositionAnimation.control = "start"
' Update displayed position timestamp ' Update displayed position timestamp
if isValid(m.global.audioPlayer.position) if isValid(m.global.audioPlayer.position)
m.lastRecordedPositionTimestamp = m.global.audioPlayer.position
m.positionTimestamp.text = secondsToHuman(m.global.audioPlayer.position, false) m.positionTimestamp.text = secondsToHuman(m.global.audioPlayer.position, false)
else else
m.lastRecordedPositionTimestamp = 0
m.positionTimestamp.text = "0:00" m.positionTimestamp.text = "0:00"
end if end if
@ -217,7 +252,9 @@ sub audioStateChanged()
if m.global.audioPlayer.state = "finished" if m.global.audioPlayer.state = "finished"
' User has enabled single song loop, play current song again ' User has enabled single song loop, play current song again
if m.global.audioPlayer.loopMode = "one" if m.global.audioPlayer.loopMode = "one"
m.scrubTimestamp = -1
playAction() playAction()
exitScrubMode()
return return
end if end if
@ -261,8 +298,28 @@ function playAction() as boolean
end function end function
function previousClicked() as boolean function previousClicked() as boolean
if m.playlistTypeCount > 1 then return false currentQueuePosition = m.global.queueManager.callFunc("getPosition")
if m.global.queueManager.callFunc("getPosition") = 0 then return false
if currentQueuePosition = 0 then return false
if m.playlistTypeCount > 1
previousItem = m.global.queueManager.callFunc("getItemByIndex", currentQueuePosition - 1)
previousItemType = m.global.queueManager.callFunc("getItemType", previousItem)
if previousItemType <> "audio"
m.global.audioPlayer.control = "stop"
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveBack")
m.global.queueManager.callFunc("playQueue")
return true
end if
end if
exitScrubMode()
m.lastRecordedPositionTimestamp = 0
m.positionTimestamp.text = "0:00"
if m.global.audioPlayer.state = "playing" if m.global.audioPlayer.state = "playing"
m.global.audioPlayer.control = "stop" m.global.audioPlayer.control = "stop"
@ -276,7 +333,6 @@ function previousClicked() as boolean
m.global.queueManager.callFunc("moveBack") m.global.queueManager.callFunc("moveBack")
pageContentChanged() pageContentChanged()
return true return true
end function end function
@ -312,7 +368,28 @@ sub setLoopButtonImage()
end sub end sub
function nextClicked() as boolean function nextClicked() as boolean
if m.playlistTypeCount > 1 then return false if m.playlistTypeCount > 1
currentQueuePosition = m.global.queueManager.callFunc("getPosition")
if currentQueuePosition < m.global.queueManager.callFunc("getCount") - 1
nextItem = m.global.queueManager.callFunc("getItemByIndex", currentQueuePosition + 1)
nextItemType = m.global.queueManager.callFunc("getItemType", nextItem)
if nextItemType <> "audio"
m.global.audioPlayer.control = "stop"
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveForward")
m.global.queueManager.callFunc("playQueue")
return true
end if
end if
end if
exitScrubMode()
m.lastRecordedPositionTimestamp = 0
m.positionTimestamp.text = "0:00"
' Reset loop mode due to manual user interaction ' Reset loop mode due to manual user interaction
if m.global.audioPlayer.loopMode = "one" if m.global.audioPlayer.loopMode = "one"
@ -379,6 +456,8 @@ sub LoadNextSong()
m.global.audioPlayer.control = "stop" m.global.audioPlayer.control = "stop"
end if end if
exitScrubMode()
' Reset playPosition bar without animation ' Reset playPosition bar without animation
m.playPosition.width = 0 m.playPosition.width = 0
m.global.queueManager.callFunc("moveForward") m.global.queueManager.callFunc("moveForward")
@ -399,9 +478,6 @@ end sub
' If we have more and 1 song to play, fade in the next and previous controls ' If we have more and 1 song to play, fade in the next and previous controls
sub loadButtons() sub loadButtons()
' Don't show audio buttons if we have a mixed playlist
if m.playlistTypeCount > 1 then return
if m.global.queueManager.callFunc("getCount") > 1 if m.global.queueManager.callFunc("getCount") > 1
m.shuffleIndicator.opacity = ".4" m.shuffleIndicator.opacity = ".4"
m.loopIndicator.opacity = ".4" m.loopIndicator.opacity = ".4"
@ -544,6 +620,96 @@ sub setBackdropImage(data)
end if end if
end sub end sub
' setSelectedButtonState: Changes the icon state url for the currently selected button
'
' @param {string} oldState - current state to replace in icon url
' @param {string} newState - state to replace {oldState} with in icon url
sub setSelectedButtonState(oldState as string, newState as string)
selectedButton = m.buttons.getChild(m.top.selectedButtonIndex)
selectedButton.uri = selectedButton.uri.Replace(oldState, newState)
end sub
' processScrubAction: Handles +/- seeking for the audio trickplay bar
'
' @param {integer} seekStep - seconds to move the trickplay position (negative values allowed)
sub processScrubAction(seekStep as integer)
' Prepare starting playStart property value
if m.scrubTimestamp = -1
m.scrubTimestamp = m.lastRecordedPositionTimestamp
end if
' Don't let seek to go past the end of the song
if m.scrubTimestamp + seekStep > m.songDuration - 5
return
end if
if seekStep > 0
' Move seek forward
m.scrubTimestamp += seekStep
else if m.scrubTimestamp >= Abs(seekStep)
' If back seek won't go below 0, move seek back
m.scrubTimestamp += seekStep
else
' Back seek would go below 0, set to 0 directly
m.scrubTimestamp = 0
end if
' Move the seedbar thumb forward
songPercentComplete = m.scrubTimestamp / m.songDuration
playPositionBarWidth = m.seekBar.width * songPercentComplete
moveSeekbarThumb(playPositionBarWidth)
' Change the displayed position timestamp
m.seekTimestamp.text = secondsToHuman(m.scrubTimestamp, false)
end sub
' resetSeekbarThumb: Resets the thumb to the playing position
'
sub resetSeekbarThumb()
m.scrubTimestamp = -1
moveSeekbarThumb(m.playPosition.width)
end sub
' moveSeekbarThumb: Positions the thumb on the seekbar
'
' @param {float} playPositionBarWidth - width of the play position bar
sub moveSeekbarThumb(playPositionBarWidth as float)
' Center the thumb on the play position bar
thumbPostionLeft = playPositionBarWidth - 10
' Don't let thumb go below 0
if thumbPostionLeft < 0 then thumbPostionLeft = 0
' Don't let thumb go past end of seekbar
if thumbPostionLeft > m.seekBar.width - 25
thumbPostionLeft = m.seekBar.width - 25
end if
' Move the thumb
m.thumb.translation = [thumbPostionLeft, m.thumb.translation[1]]
' Move the seek position element so it follows the thumb
m.seekPosition.translation = [720 + thumbPostionLeft - (m.seekPosition.width / 2), m.seekPosition.translation[1]]
end sub
' exitScrubMode: Moves player out of scrub mode state, resets back to standard play mode
'
sub exitScrubMode()
m.buttons.setFocus(true)
m.thumb.setFocus(false)
if m.seekPosition.visible
m.seekPosition.visible = false
end if
resetSeekbarThumb()
m.inScrubMode = false
m.thumb.visible = false
setSelectedButtonState("-default", "-selected")
end sub
' Process key press events ' Process key press events
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean
@ -555,9 +721,58 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true return true
end if end if
' Key Event handler when m.thumb is in focus
if m.thumb.hasFocus()
if key = "right"
m.inScrubMode = true
processScrubAction(10)
return true
end if
if key = "left"
m.inScrubMode = true
processScrubAction(-10)
return true
end if
if key = "OK" or key = "play"
if m.inScrubMode
startLoadingSpinner()
m.inScrubMode = false
m.global.audioPlayer.seek = m.scrubTimestamp
return true
end if
return playAction()
end if
end if
if key = "play" if key = "play"
return playAction() return playAction()
else if key = "back" end if
if key = "up"
if not m.thumb.visible
m.thumb.visible = true
setSelectedButtonState("-selected", "-default")
end if
if not m.seekPosition.visible
m.seekPosition.visible = true
end if
m.thumb.setFocus(true)
m.buttons.setFocus(false)
return true
end if
if key = "down"
if m.thumb.visible
exitScrubMode()
end if
return true
end if
if key = "back"
m.global.audioPlayer.control = "stop" m.global.audioPlayer.control = "stop"
m.global.audioPlayer.loopMode = "" m.global.audioPlayer.loopMode = ""
else if key = "rewind" else if key = "rewind"
@ -565,6 +780,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
else if key = "fastforward" else if key = "fastforward"
return nextClicked() return nextClicked()
else if key = "left" else if key = "left"
if m.buttons.hasFocus()
if m.global.queueManager.callFunc("getCount") = 1 then return false if m.global.queueManager.callFunc("getCount") = 1 then return false
if m.top.selectedButtonIndex > 0 if m.top.selectedButtonIndex > 0
@ -572,13 +788,17 @@ function onKeyEvent(key as string, press as boolean) as boolean
m.top.selectedButtonIndex = m.top.selectedButtonIndex - 1 m.top.selectedButtonIndex = m.top.selectedButtonIndex - 1
end if end if
return true return true
end if
else if key = "right" else if key = "right"
if m.buttons.hasFocus()
if m.global.queueManager.callFunc("getCount") = 1 then return false if m.global.queueManager.callFunc("getCount") = 1 then return false
m.previouslySelectedButtonIndex = m.top.selectedButtonIndex m.previouslySelectedButtonIndex = m.top.selectedButtonIndex
if m.top.selectedButtonIndex < m.buttonCount - 1 then m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1 if m.top.selectedButtonIndex < m.buttonCount - 1 then m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1
return true return true
end if
else if key = "OK" else if key = "OK"
if m.buttons.hasFocus()
if m.buttons.getChild(m.top.selectedButtonIndex).id = "play" if m.buttons.getChild(m.top.selectedButtonIndex).id = "play"
return playAction() return playAction()
else if m.buttons.getChild(m.top.selectedButtonIndex).id = "previous" else if m.buttons.getChild(m.top.selectedButtonIndex).id = "previous"
@ -592,6 +812,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
end if end if
end if end if
end if end if
end if
return false return false
end function end function

View File

@ -4,8 +4,9 @@
<Poster id="backdrop" opacity=".5" loadDisplayMode="scaleToZoom" width="1920" height="1200" blendColor="#3f3f3f" /> <Poster id="backdrop" opacity=".5" loadDisplayMode="scaleToZoom" width="1920" height="1200" blendColor="#3f3f3f" />
<Poster id="shuffleIndicator" width="64" height="64" uri="pkg:/images/icons/shuffleIndicator-off.png" translation="[1150,775]" opacity="0" /> <Poster id="shuffleIndicator" width="64" height="64" uri="pkg:/images/icons/shuffleIndicator-off.png" translation="[1150,775]" opacity="0" />
<Poster id="loopIndicator" width="64" height="64" uri="pkg:/images/icons/loopIndicator-off.png" translation="[700,775]" opacity="0" /> <Poster id="loopIndicator" width="64" height="64" uri="pkg:/images/icons/loopIndicator-off.png" translation="[700,775]" opacity="0" />
<Label id="positionTimestamp" width="100" height="25" horizAlign="right" font="font:SmallestSystemFont" translation="[590,825]" color="#999999" text="0:00" /> <Label id="positionTimestamp" width="100" height="25" horizAlign="right" font="font:SmallestSystemFont" translation="[590,838]" color="#999999" text="0:00" />
<Label id="totalLengthTimestamp" width="100" height="25" horizAlign="left" font="font:SmallestSystemFont" translation="[1230,825]" color="#999999" /> <Label id="totalLengthTimestamp" width="100" height="25" horizAlign="left" font="font:SmallestSystemFont" translation="[1230,838]" color="#999999" />
<LayoutGroup id="toplevel" layoutDirection="vert" horizAlignment="center" translation="[960,175]" itemSpacings="[40]"> <LayoutGroup id="toplevel" layoutDirection="vert" horizAlignment="center" translation="[960,175]" itemSpacings="[40]">
<LayoutGroup id="main_group" layoutDirection="vert" horizAlignment="center" itemSpacings="[15]"> <LayoutGroup id="main_group" layoutDirection="vert" horizAlignment="center" itemSpacings="[15]">
<Poster id="albumCover" width="500" height="500" /> <Poster id="albumCover" width="500" height="500" />
@ -16,6 +17,7 @@
<Rectangle id="seekBar" color="0x00000099" width="500" height="10"> <Rectangle id="seekBar" color="0x00000099" width="500" height="10">
<Rectangle id="bufferPosition" color="0xFFFFFF44" height="10"></Rectangle> <Rectangle id="bufferPosition" color="0xFFFFFF44" height="10"></Rectangle>
<Rectangle id="playPosition" color="#00a4dcFF" height="10"></Rectangle> <Rectangle id="playPosition" color="#00a4dcFF" height="10"></Rectangle>
<Poster id="thumb" width="25" height="25" uri="pkg:/images/icons/circle.png" visible="false" translation="[0, -10]" />
</Rectangle> </Rectangle>
<LayoutGroup id="buttons" layoutDirection="horiz" horizAlignment="center" itemSpacings="[45]"> <LayoutGroup id="buttons" layoutDirection="horiz" horizAlignment="center" itemSpacings="[45]">
<Poster id="loop" width="64" height="64" uri="pkg:/images/icons/loop-default.png" opacity="0" /> <Poster id="loop" width="64" height="64" uri="pkg:/images/icons/loop-default.png" opacity="0" />
@ -37,6 +39,9 @@
<FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[0.0, 1.0]" fieldToInterp="loop.opacity" /> <FloatFieldInterpolator key="[0.0, 1.0]" keyValue="[0.0, 1.0]" fieldToInterp="loop.opacity" />
</Animation> </Animation>
</LayoutGroup> </LayoutGroup>
<Rectangle id="seekPosition" visible="false" color="0x00000090" height="40" width="110" translation="[720, 790]">
<Label text="0:00" id="seekTimestamp" width="110" height="40" horizAlign="center" vertAlign="center" font="font:SmallestSystemFont" />
</Rectangle>
<Rectangle id="screenSaverBackground" width="1920" height="1080" color="#000000" visible="false" /> <Rectangle id="screenSaverBackground" width="1920" height="1080" color="#000000" visible="false" />
<Poster id="screenSaverAlbumCover" width="500" height="500" translation="[960,575]" opacity="0" /> <Poster id="screenSaverAlbumCover" width="500" height="500" translation="[960,575]" opacity="0" />
<Poster id="PosterOne" width="389" height="104" translation="[960,540]" opacity="0" /> <Poster id="PosterOne" width="389" height="104" translation="[960,540]" opacity="0" />

View File

@ -28,7 +28,10 @@ sub itemContentChanged()
end if end if
if isValid(itemData.indexNumber) if isValid(itemData.indexNumber)
indexNumber = itemData.indexNumber.toStr() + ". " indexNumber = `${itemData.indexNumber}. `
if isValid(itemData.indexNumberEnd)
indexNumber = `${itemData.indexNumber}-${itemData.indexNumberEnd}. `
end if
else else
indexNumber = "" indexNumber = ""
end if end if

View File

@ -27,8 +27,8 @@ sub init()
m.top.observeField("episodeNumberEnd", "onEpisodeNumberEndChanged") m.top.observeField("episodeNumberEnd", "onEpisodeNumberEndChanged")
m.top.observeField("logoImage", "onLogoImageChanged") m.top.observeField("logoImage", "onLogoImageChanged")
m.defaultButtonIndex = 1 m.defaultButtonIndex = 2
m.focusedButtonIndex = 1 m.focusedButtonIndex = 2
m.optionControlsMoved = false m.optionControlsMoved = false
m.videoControls.buttonFocused = m.defaultButtonIndex m.videoControls.buttonFocused = m.defaultButtonIndex
@ -171,7 +171,7 @@ sub resetFocusToDefaultButton()
m.videoControls.setFocus(true) m.videoControls.setFocus(true)
m.focusedButtonIndex = m.defaultButtonIndex m.focusedButtonIndex = m.defaultButtonIndex
m.videoControls.getChild(m.defaultButtonIndex).focus = true m.videoControls.getChild(m.defaultButtonIndex).focus = true
m.videoControls.buttonFocused = 1 m.videoControls.buttonFocused = m.defaultButtonIndex
m.optionControls.buttonFocused = m.optionControls.getChildCount() - 1 m.optionControls.buttonFocused = m.optionControls.getChildCount() - 1
end sub end sub

View File

@ -22,9 +22,11 @@
</ButtonGroup> </ButtonGroup>
<ButtonGroup id="videoControls" itemSpacings="[20]" layoutDirection="horiz" horizAlignment="center" translation="[960,875]"> <ButtonGroup id="videoControls" itemSpacings="[20]" layoutDirection="horiz" horizAlignment="center" translation="[960,875]">
<IconButton id="itemBack" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/itemPrevious.png" height="65" width="100" />
<IconButton id="chapterBack" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/previousChapter.png" height="65" width="100" /> <IconButton id="chapterBack" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/previousChapter.png" height="65" width="100" />
<IconButton id="videoPlayPause" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/play.png" height="65" width="100" /> <IconButton id="videoPlayPause" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/play.png" height="65" width="100" />
<IconButton id="chapterNext" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/nextChapter.png" height="65" width="100" /> <IconButton id="chapterNext" background="#070707" focusBackground="#00a4dc" padding="16" icon="pkg:/images/icons/nextChapter.png" height="65" width="100" />
<IconButton id="itemNext" background="#070707" focusBackground="#00a4dc" padding="35" icon="pkg:/images/icons/itemNext.png" height="65" width="100" />
</ButtonGroup> </ButtonGroup>
<Rectangle id="progressBarBackground" color="0x00000098" width="1714" height="8" translation="[103,970]"> <Rectangle id="progressBarBackground" color="0x00000098" width="1714" height="8" translation="[103,970]">

View File

@ -92,6 +92,35 @@ sub handleChapterSkipAction(action as string)
end if end if
end sub end sub
' handleItemSkipAction: Handles user command to skip items
'
' @param {string} action - skip action to take
sub handleItemSkipAction(action as string)
if action = "itemnext"
' If there is something next in the queue, play it
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
m.top.control = "stop"
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveForward")
m.global.queueManager.callFunc("playQueue")
end if
return
end if
if action = "itemback"
' If there is something previous in the queue, play it
if m.global.queueManager.callFunc("getPosition") > 0
m.top.control = "stop"
m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveBack")
m.global.queueManager.callFunc("playQueue")
end if
return
end if
end sub
' handleHideAction: Handles action to hide OSD menu ' handleHideAction: Handles action to hide OSD menu
' '
' @param {boolean} resume - controls whether or not to resume video playback when sub is called ' @param {boolean} resume - controls whether or not to resume video playback when sub is called
@ -220,6 +249,11 @@ sub onOSDAction()
handleShowVideoInfoPopupAction() handleShowVideoInfoPopupAction()
return return
end if end if
if action = "itemback" or action = "itemnext"
handleItemSkipAction(action)
return
end if
end sub end sub
' Only setup caption items if captions are allowed ' Only setup caption items if captions are allowed
@ -362,6 +396,7 @@ sub onVideoContentLoaded()
m.top.audioIndex = videoContent[0].audioIndex m.top.audioIndex = videoContent[0].audioIndex
m.top.transcodeParams = videoContent[0].transcodeparams m.top.transcodeParams = videoContent[0].transcodeparams
m.chapters = videoContent[0].chapters m.chapters = videoContent[0].chapters
m.top.showID = videoContent[0].showID
m.osd.itemTitleText = m.top.content.title m.osd.itemTitleText = m.top.content.title
@ -469,6 +504,11 @@ sub onNextEpisodeDataLoaded()
m.checkedForNextEpisode = true m.checkedForNextEpisode = true
m.top.observeField("position", "onPositionChanged") m.top.observeField("position", "onPositionChanged")
' If there is no next episode, disable next episode button
if m.getNextEpisodeTask.nextEpisodeData.Items.count() <> 2
m.nextupbuttonseconds = 0
end if
end sub end sub
' '
@ -583,7 +623,7 @@ sub onState(msg)
m.top.backPressed = true m.top.backPressed = true
else if m.top.state = "playing" else if m.top.state = "playing"
' Check if next episde is available ' Check if next episode is available
if isValid(m.top.showID) if isValid(m.top.showID)
if m.top.showID <> "" and not m.checkedForNextEpisode and m.top.content.contenttype = 4 if m.top.showID <> "" and not m.checkedForNextEpisode and m.top.content.contenttype = 4
m.getNextEpisodeTask.showID = m.top.showID m.getNextEpisodeTask.showID = m.top.showID

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More