Posted to tcl by Napier at Thu Oct 16 23:27:33 GMT 2014view raw

  1. # --------------------------------------------
  2. # Denon HEOS Integration
  3. # Integration with the Denon HEOS Product Line
  4. #
  5. # Author: Braden Napier
  6. # --------------------------------------------
  7. ##---------- Include URC SDK Files: ----------##
  8. source [file dirname [info script]]/URC_SDK.tcl
  9.  
  10. # ------------- SYSTEM SETTINGS -------------
  11. # System Logging: 1 = ON, 0 = OFF (Turn OFF Before Final Deployment)
  12. # Showroom Variable handles the "Demo / Showroom" Mode of the Module
  13. # --------------------------------------------
  14. enableLOG 0
  15. # --------------------------------------------
  16. set_visible "room_2_name" 0
  17. set_visible "nowplaying_2_indicator" 0
  18. set_visible "nowplaying_2_songinfo" 0
  19. set_visible "nowplaying_2_artwork" 0
  20. set_visible "player2" 0
  21. set_visible "nowplaying_3_indicator" 0
  22. set_visible "room_3_name" 0
  23. set_visible "nowplaying_3_songinfo" 0
  24. set_visible "nowplaying_3_artwork" 0
  25. set_visible "player3" 0
  26.  
  27. # TODO: ::Widgets::Rooms::UpdateList: Get Name of Room based on ID, if Unknown Get or Display Provided Value
  28. # TODO: ::Events::State Need to set the variable "i" to be the index of the pid if required.
  29.  
  30. #### System Variable Value Examples
  31. ## Active Players Without Group:
  32. # -1367676363 {name Kitchen pid -1367676363 model {HEOS 3} version 1.239.93 type player} 479981077 {name {Office Desk} pid 479981077 model {HEOS 3} version 1.239.93 type player}
  33. ## Active Players With Group:
  34. # -1367676363 {name {Kitchen + Office Desk} gid -1367676363 players {{name {Office Desk} pid 479981077 role member} {name Kitchen pid -1367676363 role leader}} type group}
  35. ## Players:
  36. # {name {Office Desk} pid 479981077 gid -1367676363 model {HEOS 3} version 1.239.93} {name Kitchen pid -1367676363 gid -1367676363 model {HEOS 3} version 1.239.93}
  37. ## Player State:
  38. # 479981077 {pid 479981077 state stop nowplaying {type station song Shuffle station {} artist {} image_url {} mid 62572596943575335 qid 1 gid -1367676363}}
  39. # -1367676363 {pid -1367676363 state stop nowplaying {type station song Shuffle station {} artist {} image_url {} mid 62572596943575335 qid 1 gid -1367676363}}
  40. ####
  41.  
  42. #### pid Index
  43. ## Variable Name:
  44. # pid Index is the currently showing listing of players in the main menu. This is to provide what players are currently showing in the
  45. # Rooms Menu. The value of the index will provide either the pid or gid dependent on if the resulting option is a group or not.
  46. # -> 1 {pid $pid} 2 {gid $gid}
  47. ####
  48. ## Variable Name: $::Module::totalPlayers
  49. # This variable reflects the total number of players in the account that are active. Should grouping occur, the grouped players will
  50. # be represented as a single player.
  51. ####
  52. ## Variable Name: $::Module::selectedRoom
  53. # This Variable reflects the index of the currently selected room/player/group.
  54. ## Variable Name: $::Module::selectedPage
  55. # This Variable reflects the page number of the currently selected room/player/group.
  56. ## Variable Name: $::Module::selectedIndex
  57. # This Variable reflects the overall index of the selected room/player/group.
  58. ## Variable Name: $::Module::currentPage
  59. # This Variable reflects the current page being viewed by the end-user.
  60. ####
  61. #### System Variables & Descriptions:
  62. ## $::Widgets::NowPlaying::PlayPauseState
  63. # Provides the Current State of the Play/Pause Button on the Now Playing Page (0 = Pause Icon, 1 = Play Icon)
  64. ## $::Widgets::DefaultArtwork
  65. # Provides path to the default Artwork to use when Album Art is not available.
  66. ## $::Widgets::Rooms::CurrentlyDisplayed
  67. # Nested Dictionary with the keys being the index of the list and the value being either the pid or gid
  68. ####
  69.  
  70.  
  71.  
  72. namespace eval Module {
  73. variable activePage 0
  74. variable totalActivePlayers -1
  75. variable selectedRoom 1
  76. variable selectedPage 1
  77. variable selectedIndex 1
  78.  
  79. variable currentPage 1
  80. variable activeIndex [dict create 1 479981077 2 -1367676363]
  81.  
  82. variable isConnected 0
  83. variable wasConnectedPreviously 0
  84. variable Players ""
  85. variable CurrentRoom ""
  86. variable ActivePlayers ""
  87. variable playerStatus ""
  88. variable sourceList ""
  89. variable moduleHeartbeatTimer ""
  90. variable isAuthorized 0
  91. variable isAuthorizing 0
  92. variable currentKeyboard ""
  93. variable userCredentials
  94. variable pid "479981077"
  95.  
  96. variable selectedpid ""
  97. variable nowPlayingTime ""
  98. variable timedSong ""
  99. variable everyIndicator ""
  100. variable timedSongDuration ""
  101. variable timedSongElapsed ""
  102. variable prevPlayerStatus ""
  103. variable selectedpidMuted 0
  104. variable playpauseDelay 0
  105. }
  106.  
  107. proc changePage {p} {display_page "$p"; set ::Module::activePage "$p"}
  108.  
  109. namespace eval Clients {
  110. # The Clients Namespace provides variables that are specific to the interface running the module.
  111. # These will be created at startup with values based upon the URC Interface launching the script.
  112.  
  113. # $::Clients::totalPages will be updated based on how many players are currently available. This
  114. # is evaluated each time players are received from the System Script.
  115. variable totalPages 1
  116. variable playersPerPage 1
  117. switch -- $::varIfClientType {
  118. TRC-1080 {
  119. set ::Clients::playersPerPage 3
  120. }
  121. TRC-1280 {}
  122. TKP-7000 {
  123. set ::Clients::playersPerPage 5
  124. }
  125. TKP-2000 {}
  126. MRX-20 {
  127. set ::Clients::playersPerPage 5
  128. }
  129. }
  130. }
  131.  
  132. # ------------- Session Control -------------
  133. # Handles Control of Various Sessions
  134. # --------------------------------------------
  135.  
  136. LOG "--- SESSION STARTED ---"
  137. LOG "Denon HEOS Session Started"
  138. LOG "Module Connection from $varIfClientType"
  139. LOG "Resetting Module Global Variables"
  140. LOG "-----------------------"
  141.  
  142.  
  143. proc onClientDisconnect {} {
  144. LOG "--- SESSION CLOSED ---"
  145. LOG "Module Name: Denon HEOS Module, Client: $::varIfClientType"
  146. LOG "-----------------------"
  147. # device_proc "::URC::moduleCallback -session closed -client $::varIfClientType"
  148. }
  149.  
  150. namespace eval Events {
  151. # Events from the Denon HEOS are Called by the System Script here. These procedures
  152. # will generally update the UI's that are currently open.
  153. # ::Events::State -state play -pid $pid
  154. # ::Events::MediaProgess
  155. # ::Events::NewMedia -pid $pid -song "Come Together" -artist "The Beatles" -album "Abbey Road" -image $url -service "Pandora"
  156.  
  157. proc GroupingChanged {args} {
  158. # Called by System Script when Grouping is Changed
  159. }
  160.  
  161. proc PlayersChanged {args} {
  162. # Called by System Script when the Players have changed
  163. systemCall getPlayers
  164. }
  165.  
  166. proc ConnectionState {state} {
  167. # Sets the isConnected Variable to the received Connection State.
  168. # 0 = Disconnected , 1 = Online
  169. # - This will call the Callback to Update the UI Appropriately
  170. set ::Module::isConnected $state
  171. }
  172.  
  173. proc PlayersIndex {tempDict} {
  174. # DEPRECIATED ???
  175. # Called by the System Script when a players "Index" has changed. This could be due to
  176. # reordering of a list, regrouping, etc.
  177. # $tempDict will hold the new dictionary value.
  178.  
  179. set ::Module::activeIndex $tempDict
  180. }
  181.  
  182. proc State {args} {
  183. # Updates the Play State of the Players
  184. # Arguments are:
  185. # -state : <play, stop, pause>
  186. # -pid : The player id of the player that was changed
  187. # -gid : The group id of the player if a group is present
  188. if {[dict exists $args -state]} {set state [dict get $args -state]} else {LOG "No State Detected in Events::State Procedure Event"; return}
  189. if {[dict exists $args -pid]} {set id [dict get $args -pid]} else {LOG "No PID Provided for Player State Event"; set id 0}
  190. if {[dict exists $args -gid]} {set id [dict get $args -gid]} else {LOG "No GID Provided for Player State Event"}
  191. if {$id == "0"} {LOG "ERROR: No Player or Group ID Provided in ::Events::State"; return}
  192. # TODO: ::Events::State Need to set the variable "i" to be the index of the pid if required.
  193. # Note: If pid is not currently being shown must save to info dict
  194.  
  195. set index [::Module::getIndexbyID $id]
  196. set pageOfPlayer [expr {floor($index / $::Clients::playersPerPage) + 1}]
  197. set i 0
  198. if {$pageOfPlayer == $::Module::currentPage} {
  199. # Checks to see if the Player is currently displayed on the "Rooms" page based on the "$::Module::currentPage" variable.
  200. # If it is visible, it will provide the player # to update with the new Play State Value
  201. set i [expr {($index % $::Clients::playersPerPage) + 1}]
  202. }
  203.  
  204. switch -- $state {
  205. "play" {
  206. if {$i != 0} {catch {::set_text "nowplaying_${i}_indicator" "b"}}
  207. # Set the Play Pause Icon on Now Playing Page to Pause Icon
  208. if {$::Module::selectedpid == $id} {::Widgets::NowPlaying::PlayPause -state pause}
  209. }
  210. "pause" {
  211. if {$i != 0} {catch {set_text "nowplaying_${i}_indicator" "i"}}
  212. # Set the Play Pause Icon on Now Playing Page to Play Icon
  213. if {$::Module::selectedpid == $id} {::Widgets::NowPlaying::PlayPause -state play}
  214. }
  215. "stop" {
  216. if {$i != 0} {catch {set_text "nowplaying_${i}_indicator" "j"}}
  217. # Set the Play Pause Icon on Now Playing Page to Play Icon
  218. if {$::Module::selectedpid == $id} {::Widgets::NowPlaying::PlayPause -state play}
  219. }
  220. default {LOG "State Switch in State Event Failure"}
  221. }
  222.  
  223. }
  224.  
  225. proc MediaProgress {ARGS} {
  226. # Updates the Media Progress details of the HEOS Player
  227. }
  228.  
  229. proc NewMedia {args} {
  230. # Called by the System Script to update media metadata for a player or group
  231. # Arguments are:
  232. # -pid : The player id that is being updated.
  233. # -gid : The group id that is being updated.
  234. # -song : The name of the Song that is now playing.
  235. # -artist : The name of the Artist that is now playing.
  236. # -album : The name of the Album that is now playing.
  237. # -image : An URL to the image that is now playing. If not specified default will be used.
  238. # -service : The name of the service that is now playing.
  239. LOG "Executing ::Events::NewMedia with args\n$args"
  240. if {[dict exists $args -song]} {set song [dict get $args -song]} else {LOG "No Song Name Provided"; set song ""}
  241. if {[dict exists $args -artist]} {set artist [dict get $args -artst]} else {LOG "No Artist Provided"; set artist ""}
  242. if {[dict exists $args -album]} {set artist [dict get $args -album]} else {LOG "No Album Provided"; set album ""}
  243. if {[dict exists $args -image]} {set image [dict get $args -image]} else {LOG "No Artwork Provided"; set image 0}
  244. if {[dict exists $args -service]} {set service [dict get $args -service]} else {LOG "No Service Provided"; set service ""}
  245. if {[dict exists $args -pid]} {set id [dict get $args -pid]; set playerType "Player"} else {LOG "No PID Provided, is it a Group?"; set playerType 0}
  246. if {[dict exists $args -gid]} {set id [dict get $args -gid]; set playerType "Group"} else {LOG "No GID Provided"}
  247. if {$playerType == "0"} {LOG "ERROR: Player or Group ID Not Provided"; return}
  248.  
  249. LOG "New Media for $playerType ${id}:\nService: $service\nSong $song\nArtist: $artist\nAlbum: $album\nImage URL: $image"
  250.  
  251. if {$id == $::Module::selectedpid} {
  252. # Currently Playing Player/Group - Update "Now Playing" Page
  253. ::Widgets::NowPlaying::Metadata -song $song -artist $artist -album $album -service $service
  254. ::Widgets::NowPlaying::Artwork $image
  255. }
  256. set pageOfPlayer [::Module::whatPage $id]
  257. if {$::Module::currentPage == $pageOfPlayer} {
  258. set position [::Module::positionOnPage $id]
  259. ::Widgets::Rooms::UpdateList -i $position -song $song -artist $artist -image $image -id $id
  260. }
  261.  
  262. # Called after updating the UI (for speed) to get the entire Player Status Dictionary from the System Script
  263. systemCall getPlayerStatus
  264. }
  265.  
  266. proc UpdateAll {} {
  267. # This procedure is called to update all UI Elements based on the current
  268. # status from the System Script. It will query the current data from the System
  269.  
  270. systemCall getAuthorized
  271.  
  272. ::Widgets::NowPlaying::Refresh
  273. ::Widgets::Rooms::UpdateListsPage
  274. }
  275.  
  276. }
  277.  
  278. namespace eval Update {
  279.  
  280. proc ActivePlayers {} {
  281. # Updates the Active Players Variables and all associated values that may be affected by a change
  282. # in the value.
  283.  
  284. systemCall getActivePlayers
  285. catch {
  286. set pids [dict keys $::Module::ActivePlayers]
  287. set ::Module::totalActivePlayers [llength $pids]
  288. set ::Clients::totalPages [expr {ceil(double($::Module::totalActivePlayers) / $::Clients::playersPerPage)}]
  289. } { LOG "ERROR: While Updating Active Players in ::Update::ActivePlayers" }
  290. return
  291. }
  292.  
  293. proc playerStatus {} {
  294. # Updates the Player Status Variable which includes all players now playing information & states.
  295. systemCall getPlayerStatus
  296. }
  297.  
  298. }
  299.  
  300. namespace eval Widgets {
  301.  
  302. variable DefaultArtwork "C:\tkp7-art-missing.png"
  303.  
  304.  
  305. namespace eval Rooms {
  306. # $::Widgets::Rooms::CurrentlyDisplayed is a Dictionary with the keys being the index of the list and the value being either the pid or gid
  307. # -> 1 {pid $pid} 2 {gid $gid} 3 {pid $pid} 4 {pid $pid}
  308. variable CurrentlyDisplayed [dict create]
  309.  
  310. proc Set {args} {
  311. # Sets/Refreshes the Rooms Listing in the "Rooms" Page.
  312.  
  313. }
  314.  
  315. proc UpdateList {args} {
  316. # Updates an Item in the Rooms Listing of the "Rooms" Page
  317. # Arguments Are:
  318. # -i : The list item number to update in the Rooms Listing
  319. # -song : The text to play in the "Song" field of the provided index
  320. # -artist : The text to put in the "Artist" field of the provided index
  321. # -image : The artwork URL to display, 0 or undefined for default artwork
  322. # -id : The pid or gid of the player, this is used to get the name. A name can also be provided instead.
  323. if {[dict exists $args -i]} {set i [dict get $args -index]} else {LOG "ERROR: No Index Provided in ::Widgets::Rooms::UpdateList"; return}
  324. if {[dict exists $args -song]} {set song [dict get $args -song]} else {LOG "No Song Name Provided"; set song ""}
  325. if {[dict exists $args -artist]} {set artist [dict get $args -artist]} else {LOG "No Artist Provided"; set artist ""}
  326. if {[dict exists $args -image]} {set image [dict get $args -image]} else {set image 0}
  327. if {[dict exists $args -id]} {set id [dict get $args -id]} else {LOG "ERROR: No ID Provided in ::Widgets::Rooms::UpdateList"; set id 0}
  328. if {$artist == "" && $song == ""} {set songinfo "Refreshing..."} else {set songinfo "${artist} - ${song}"}
  329. if {$image == "0" || $image == ""} {set image $::Widgets::DefaultArtwork}
  330.  
  331. if {$i > $::Clients::playersPerPage} {LOG "ERROR: Attempted to Update a List Item Number Not Supported by the Current Client"; return}
  332. LOG "Updating Rooms Listing\nIitem: ${i}\nArtist: ${artist}\nSong: ${song}"
  333. # TODO: ::Widgets::Rooms::UpdateList: Get Name of Room based on ID, if Unknown Get or Display Provided Value
  334. if {![dict exists $::Module::ActivePlayers $id]} {LOG "ERROR: Provided ID in ::Widgets::Rooms::UpdateList does not exist"; return}
  335. set name [dict get $::Module::ActivePlayers $id name]
  336. catch {
  337. ::set_text "room_${i}_name" $name
  338. ::set_text "nowplaying_${i}_songinfo" $songinfo
  339. ::set_image "nowplaying_${i}_artwork" $image
  340. }
  341. }
  342.  
  343. proc UpdateListsPage {} {
  344. # Called whenever the "$::Module::currentPage" variable is changed and will update the
  345. # Rooms/Players/Groups list to reflect the current page.
  346. # Note: This can also be used to refresh the lists metadata (however this should be done automatically)
  347. ::Update::ActivePlayers
  348. ::Update::playerStatus
  349.  
  350. if {$::Module::currentPage > $::Clients::totalPages} {
  351. # Attempting to Update a Page Which Doesn't Exist, will instead jump to the last available page.
  352. LOG "Attempted to jump to a Rooms Page that doesn't exist, jumping to the last available page"
  353. set ::Module::currentPage $::Clients::totalPages
  354. return
  355. } else {
  356. set pids [dict keys $::Module::ActivePlayers]
  357. set i 1
  358. while {$i <= $::Clients::playersPerPage} {
  359. if {$i > $::Module::totalActivePlayers} {break}
  360. set index [expr {(($::Module::currentPage * $::Clients::playersPerPage) + ($i - 1)) - $::Clients::playersPerPage}]
  361. set id [::Module::getIDbyIndex $index]
  362. # TODO : COMPLETE List Updating
  363. ::Widgets::UpdateList \
  364. -i $i \
  365. -song [dict get $::Module::playerStatus $id nowplaying song] \
  366. -artist [dict get $::Module::playerStatus $id nowplaying artist] \
  367. -image [dict get $::Module::playerStatus $id nowplaying image_url] \
  368. -id $id
  369. incr i
  370. }
  371. LOG "Done Parsing Players for Rooms Listing"
  372. }
  373. }
  374.  
  375. proc UpdateState {ARGS} {
  376. # Updates the Play State of the specified player.
  377. }
  378.  
  379. proc Scroll {args} {
  380. ## Use to Scroll the Rooms Listing Up or Down based on the arguments provided
  381. ## Arguments:
  382. # -direction <up, down> - Used to Navigate the Rooms List Up or Down
  383. # -reset <1, 0> - Boolean if Reset should be done (scroll to top of the list)
  384. if {[dict exists $args -reset]} {set reset [dict get $args -reset]} else {set reset 0}
  385. if {[dict exists $args -direction]} {
  386. set direction [dict get $args -direction]
  387. } elseif {!$reset} {
  388. LOG "ERROR: No Direction and Not Being Reset in ::Widgets::Rooms::Scroll"
  389. return
  390. }
  391.  
  392. if {$reset} {
  393. set ::Module::currentPage 1
  394. ::Widgets::Rooms::UpdateListsPage
  395. } else {
  396. switch -- $direction {
  397. up {
  398. if {$::Module::currentPage == $::Clients::totalPages}
  399. }
  400. down {
  401. # Scroll the Rooms List Down
  402. }
  403. default {LOG "ERROR: Direction Doesn't Match a valid option in ::Widgets::Rooms::Scroll"}
  404. }
  405. }
  406. }
  407. }
  408.  
  409. namespace eval NowPlaying {
  410. variable PlayPauseState -1
  411.  
  412. proc Refresh {} {
  413. # This Procedure is used to Refresh the "Now Playing" listed according to the currently selected player/groups now playing data.
  414. ::Update::PlayerStatus
  415. set id $::Module::selectedpid
  416.  
  417. catch {
  418. set name [dict get $::Module::ActivePlayers $id name]
  419. ::set_text "current_room_rooms" $name
  420. ::set_text "current_room_nowplaying" $name
  421. ::set_text "current_room_sources" $name
  422. ::set_text "current_room_pandora" $name
  423. } {LOG "ERROR: While Setting Text of Selected Room Name in ::Widgets::NowPlaying::Refresh"}
  424.  
  425. if {[dict exists $::Module::playerStatus $id nowplaying song]} {set song [dict get $::Module::playerStatus $id nowplaying song]} else {set song ""}
  426. if {[dict exists $::Module::playerStatus $id nowplaying artist]} {set artist [dict get $::Module::playerStatus $id nowplaying artist]} else {set artist ""}
  427. if {[dict exists $::Module::playerStatus $id nowplaying album]} {set album [dict get $::Module::playerStatus $id nowplaying album]} else {set album ""}
  428. if {[dict exists $::Module::playerStatus $id nowplaying image_url]} {set image [dict get $::Module::playerStatus $id nowplaying image_url]} else {set image 0}
  429. if {[dict exists $::Module::playerStatus $id nowplaying service]} {set service [dict get $::Module::playerStatus $id nowplaying service]} else {set service ""}
  430.  
  431. ::Widgets::NowPlaying::Metadata -song $song -artist $artist -album $album -service $service
  432. ::Widgets::NowPlaying::Artwork $image
  433. }
  434.  
  435. proc SelectedRoom {id} {
  436. # This Procedure should be called whenever the "Selected Room/Player/Group" has changed. It will take
  437. # check to make sure the id is a valid player, update the selected player variable, then call to refresh
  438. # the UI as needed.
  439. if {![dict exists $::Module::playerStatus $id]} {LOG "ERROR: Provided ID Doesn't Exist in ::Widgets::NowPlaying::SelectedRoom"; return}
  440. set ::Module::selectedpid $id
  441.  
  442. # Call the Now Playing Refresh Procedure to update the Now Playing page for the newly selected Player/Group
  443. # will also update the "Current Room" titles throughout the UI.
  444. ::Widgets::NowPlaying::Refresh
  445.  
  446. }
  447.  
  448. proc Metadata {args} {
  449. # Updates the Metadata Text for the Now Playing Song on the Now Playing Page
  450. # Arguments Are:
  451. # -song : Name of the Song to put into the Song Info Field
  452. # -artist : Name of the Artist to put into the Artist Info Field
  453. # -album : Name of the Album to put into the Album Info Field
  454. # -service : Name of the Service Currently Being Used
  455. if {[dict exists $args -song]} {set song [dict get $args -song]} else {set song ""}
  456. if {[dict exists $args -artist]} {set artist [dict get $args -artist]} else {set artist ""}
  457. if {[dict exists $args -album]} {set album [dict get $args -album]} else {set album ""}
  458. if {[dict exists $args -service]} {set service [dict get $args -service]} else {set service ""}
  459. catch {
  460. ::set_text "nowplaying_artist" $artist
  461. ::set_text "nowplaying_song" $song
  462. ::set_text "nowplaying_album" $album
  463. } {LOG "ERROR: While Setting Metadata Text as Specified on Now Playing Page in ::Widgets::NowPlaying::Metadata"}
  464. }
  465.  
  466. proc Artwork {url} {
  467. # Sets the Artwork on the Now Playing Page to the Specified URL
  468. # Example Call:
  469. # ::Widgets::NowPlaying::Artwork http://www.website.com/image.png
  470. if {$url == "0" || $url == ""} {set url $::Widgets::DefaultArtwork}
  471. catch {::set_image "nowplaying_artwork" $url}
  472. }
  473.  
  474. proc PlayPause {args} {
  475. # Controls the PlayPause Multi-State Button available in the "Now Playing" Page.
  476. # Stores the current state in $::Widgets::NowPlaying::PlayPauseState
  477. # 0 = Pause Icon, 1 = Play Icon
  478. # Arguments:
  479. # -state : Provide the State the Button should change to. 0 = Pause Icon, 1 = Play Icon
  480. # Example Calls:
  481. # ::Widgets::NowPlaying::PlayPause -state <0, pause, stop>
  482. # ::Widgets::NowPlaying::PlayPause -state <1, play>
  483.  
  484. if {[dict exists $args -state]} {set state [dict get $args -state]} else {return -1}
  485. switch -- $state {
  486. 1 -
  487. play {set state 1}
  488. 0 -
  489. pause -
  490. stop {set state 0}
  491. default {LOG "Invalid Switch in PlayPause Widget"}
  492. }
  493. LOG "Setting "
  494. catch {::set_state "btn_playpause" $state} {LOG "Set PlayPause Button State Failed"; return -1}
  495. set ::Widgets::NowPlaying::PlayPauseState $state
  496. }
  497. }
  498. }
  499.  
  500. namespace eval Module {
  501. proc Startup {args} {
  502. # Called when the Module is First Launched - this procedure will gather the
  503. # data from the System Script and populate the UI properly. Once it has loaded
  504. # the module will jump to the rooms menu.
  505.  
  506. # Load the Please Wait Page while the Module Loads Details from the System Script
  507. changePage "Please Wait"
  508.  
  509. # A trace is placed on the "$::Module::isConnected" variable to call the Callbacks::ConnectionState
  510. # procedure when the variables state changes. If not connected, it will jump to the not connected
  511. # page until a connection is successful. If connected, it will jump to Please Wait, load all the
  512. # information from the System Script, then jump to the Rooms Page.
  513. ::Tools::setTrace ::Module::isConnected ::Callbacks::ConnectionState
  514. ::Tools::setTrace ::Module::currentPage ::Callbacks::currentPage
  515. set ::Module::isConnected [systemCall checkConnection]
  516. }
  517.  
  518. proc getIDbyIndex {index} {
  519. # This Procedure will take an Index Value and respond with the pid or gid.
  520.  
  521. set selectedPlayer [lindex [dict keys $::Module::ActivePlayers] $index]
  522. if {$selectedPlayer == ""} {LOG "ERROR: Illegal Index Provided in ::Module::getIDbyIndex"; return -1}
  523. return $selectedPlayer
  524. }
  525.  
  526. proc getIndexbyID {id} {
  527. # This procedure will return the index of the provided Room/Player/Group ID.
  528. # Note: Index starts at 0 not 1
  529.  
  530. set index [lsearch [dict keys $::Module::ActivePlayers] $id]
  531. if {$index == -1} {LOG "ERROR: ID Provided is not a currently Active Player, it may be grouped and not acting as the group leader?"; return -1}
  532. return $index
  533. }
  534.  
  535. proc whatPage {id} {
  536. # Checks what page a given ID should be on. Returns -1 if the ID is not found in the Active Players listing.
  537. if {![dict exists $::Module::ActivePlayers $id]} {LOG "ERROR: Provided ID in ::Module::whatPage is not an Active Player"; return -1}
  538. set index [lsearch [dict keys $::Module::ActivePlayers] $id]
  539. set pageOfPlayer [expr {floor($index / $::Clients::playersPerPage) + 1}]
  540. return $pageOfPlayer
  541. }
  542.  
  543. proc positionOnPage {id} {
  544. # Checks what position on its page the provided Player ID should be. For example, if the player is on page but list item 1, it will
  545. # return "1" - to get the page use ::Module::whatPage
  546. set index [lsearch [dict keys $::Module::ActivePlayers] $id]
  547. set position [expr {($index % $::Clients::playersPerPage) + 1}]
  548. return $position
  549. }
  550. }
  551.  
  552. namespace eval Callbacks {
  553.  
  554. proc ConnectionState {oldValue callbackName varName args} {
  555. # This is called whenever the $::Module::isConnected variable is changed using the Trace Functions that are called
  556. # at the module startup.
  557.  
  558. LOG "CALLBACK: Connection State"
  559. upvar 1 $varName newValue
  560. # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
  561. uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
  562. uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
  563. if {$oldValue != $newValue} {
  564. if {$newValue == "0"} {
  565. LOG "Connection: Lost Connection to the HEOS System"
  566. changePage "Not Connected"
  567. } elseif {$newValue == "1"} {
  568. LOG "Connection: Connected or Re-Connected to the HEOS System"
  569. changePage "Please Wait"
  570. ::Events::UpdateAll
  571. changePage "Rooms"
  572. }
  573. } else {LOG "Connection: Variable was set but remains unchanged - State is $newValue"}
  574. }
  575.  
  576. proc currentPage {oldValue callbackName varName args} {
  577. # This is called whenever the $::Module::currentPage variable is changed using the Trace Functions that are called
  578. # at the module startup.
  579.  
  580. LOG "CALLBACK: Current Rooms Page"
  581. upvar 1 $varName newValue
  582. # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
  583. uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
  584. uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
  585.  
  586. # Update the Rooms/Players/Groups Listing to reflect the new pages values:
  587. ::Widgets::Rooms::UpdateListsPage
  588. }
  589. }
  590.  
  591.  
  592. proc moduleStartup {} {
  593. set ::Module::isConnected [systemCall checkConnection]
  594. if {$::Module::isConnected} {
  595. if {!$::Module::wasConnectedPreviously} {
  596. ::Tools::setTrace ::Module::ActivePlayers ::UpdateUI::Players
  597. ::Tools::setTrace ::Module::playerStatus ::UpdateUI::playerStatus
  598. ::Tools::setTrace ::Module::isAuthorized ::UpdateUI::getCredentials
  599. ::Tools::setTrace ::Module::CurrentRoom ::UpdateUI::CurrentRoom
  600. redirectHardButtons
  601. }
  602. moduleHeartbeat
  603. changePage "Rooms"
  604. catch {::every cancel moduleHeartbeat}
  605. catch {::every cancel moduleStartup}
  606. ::every 5000 moduleHeartbeat
  607. set ::Module::wasConnectedPreviously 1
  608. # device_proc "::URC::moduleCallback -session opened -client $::varIfClientType"
  609. } else {
  610. LOG "Can't Connect! Will try again in 5 Seconds"
  611. changePage "NotConnected"
  612. catch {::every cancel moduleStartup}
  613. catch {::every cancel moduleHeartbeat}
  614. ::every 5000 moduleStartup
  615. }
  616. }
  617.  
  618. # --------------------------------------------
  619.  
  620. # ------------- Hard/Soft Button Events -------------
  621. # Handles Hard Button Presses while in the Module
  622. # -----------------------------------------------
  623. proc hardbutton_release {id} {
  624. switch -- $id {
  625. "$::HKEY_LEFT" {LOG "Left Button Event"}
  626. "$::HKEY_RIGHT" {LOG "Right Button Event"}
  627. "$::HKEY_UP" {LOG "Up Button Event"}
  628. "$::HKEY_DOWN" {LOG "Down Button Event"}
  629. "$::HKEY_SELECT" {LOG "Select Button Event"}
  630. default {LOG "Unknown Button Release Received"}
  631. }
  632. }
  633. proc redirectHardButtons {} {
  634. # redirect_hardbutton $::HKEY_PAGEPREV 1
  635. # redirect_hardbutton $::HKEY_PAGENEXT 1
  636. redirect_hardbutton $::HKEY_UP 1
  637. redirect_hardbutton $::HKEY_DOWN 1
  638. redirect_hardbutton $::HKEY_LEFT 1
  639. redirect_hardbutton $::HKEY_RIGHT 1
  640. redirect_hardbutton $::HKEY_SELECT 1
  641. # redirect_hardbutton $::HKEY_CHUP 1
  642. # redirect_hardbutton $::HKEY_CHDN 1
  643. }
  644. proc btn_handler {event id} {
  645. # Events: 1 CLICK, 2 PRESS, 3 RELEASE
  646. if {$event == 3} {
  647. switch -regexp -- $id {
  648. "btn_1" {
  649. # Selected Player 1
  650. set pid [lindex [dict keys $::Module::ActivePlayers] 0]
  651. if {$pid == ""} {return}
  652. set ::Module::CurrentRoom [dict get $::Module::ActivePlayers $pid name]
  653. set ::Module::selectedRoom 1
  654. set ::Module::selectedpid $pid
  655. systemCall getActivePlayers
  656. systemCall getPlayerStatus
  657. }
  658. "btn_2" {
  659. # Selected Player 2
  660. set pid [lindex [dict keys $::Module::ActivePlayers] 1]
  661. if {$pid == ""} {return}
  662. set ::Module::CurrentRoom [dict get $::Module::ActivePlayers $pid name]
  663. set ::Module::selectedRoom 2
  664. set ::Module::selectedpid $pid
  665. systemCall getActivePlayers
  666. systemCall getPlayerStatus
  667.  
  668. }
  669. "btn_3" {
  670. # Selected Player 3
  671. set pid [lindex [dict keys $::Module::ActivePlayers] 2]
  672. if {$pid == ""} {return}
  673. set ::Module::CurrentRoom [dict get $::Module::ActivePlayers $pid name]
  674. set ::Module::selectedRoom 3
  675. set ::Module::selectedpid $pid
  676. systemCall getActivePlayers
  677. systemCall getPlayerStatus
  678.  
  679. }
  680. "^player_.*" {
  681. set pressed [::Tools::extract_numbers $id]
  682. set index [expr {(($::Module::currentPage * $::Clients::playersPerPage) + ($pressed - 1)) - $::Clients::playersPerPage}]
  683. set pid [::Module::getIDbyIndex $index]
  684. ::Widgets::NowPlaying::SelectedRoom $pid
  685.  
  686. }
  687. "btn_nowplaying" {changePage "NowPlaying"}
  688. "btn_volup" {
  689. if {[dict exists $::Module::ActivePlayers $::Module::selectedpid gid]} {
  690. device_proc "::Control::group_volume up [dict get $::Module::ActivePlayers $::Module::selectedpid gid]"
  691. } else {
  692. device_proc "::Control::volume up $::Module::selectedpid"
  693. }
  694.  
  695. }
  696. "btn_voldown" {
  697. if {[dict exists $::Module::ActivePlayers $::Module::selectedpid gid]} {
  698. device_proc "::Control::group_volume down [dict get $::Module::ActivePlayers $::Module::selectedpid gid]"
  699. } else {
  700. device_proc "::Control::volume down $::Module::selectedpid"
  701. }
  702. }
  703. "btn_mute" {
  704. if {$::Module::selectedpidMuted} {
  705. if {[dict exists $::Module::ActivePlayers $::Module::selectedpid gid]} {
  706. device_proc "::Control::group_volume muteoff [dict get $::Module::ActivePlayers $::Module::selectedpid gid]"
  707. } else {
  708. device_proc "::Control::volume muteoff $::Module::selectedpid"
  709. }
  710. set_text "btn_mute" "pq" -1 0
  711. set ::Module::selectedpidMuted 0
  712.  
  713. } else {
  714. if {[dict exists $::Module::ActivePlayers $::Module::selectedpid gid]} {
  715. device_proc "::Control::group_volume muteon [dict get $::Module::ActivePlayers $::Module::selectedpid gid]"
  716. } else {
  717. device_proc "::Control::volume muteon $::Module::selectedpid"
  718. }
  719. set_text "btn_mute" "p" -1 0
  720. set ::Module::selectedpidMuted 1
  721. }
  722. }
  723. "btn_skip_forward" {device_proc "::Control::Skip next $::Module::selectedpid"; after 1000 {systemCall getPlayerStatus}}
  724. "btn_skip_back" {device_proc "::Control::Skip previous $::Module::selectedpid"; ; after 1000 {systemCall getPlayerStatus}}
  725. "btn_home" -
  726. "btn_rooms" {changePage "Rooms"}
  727. ".*goback_sources" -
  728. "btn_music_source" {changePage "MusicSources"}
  729. "btn_src_goback.*" {changePage "Rooms"}
  730. "btn_src_pandora" {changePage "Pandora"}
  731. "btn_src_napster" {changePage "Napster"}
  732. "btn_src_spotify" {changePage "Spotify"}
  733. "btn_src_tunein" {changePage "tunein"}
  734. "btn_src_rhapsody" {changePage "Rhapsody"}
  735. ".*_beta" {catch {system_notification "Coming Soon" "This Feature is Coming Soon!" "Continue"}}
  736. "btn_ExitMain" {simulate_hardbutton $::HKEY_MAIN 1; simulate_hardbutton $::HKEY_MAIN 0}
  737. default {LOG "Unknown Button Release Event"}
  738. }
  739. }
  740. }
  741.  
  742. proc multibtn_handler {event id} {
  743. if {$event == 3} {
  744. switch -regexp -- $id {
  745. "btn_playpause" {
  746. switch -- [dict get $::Module::playerStatus $::Module::selectedpid state] {
  747. "play" {
  748. device_proc "::Control::setPlayState pause $::Module::selectedpid"
  749. set ::Module::playpauseDelay 1
  750. set_state "btn_playpause" 1
  751. after 5000 {set ::Module::playpauseDelay 0}
  752. dict set ::Module::playerStatus $::Module::selectedpid state pause
  753. }
  754. "pause" {
  755. device_proc "::Control::setPlayState play $::Module::selectedpid"
  756. set ::Module::playpauseDelay 1
  757. set_state "btn_playpause" 0
  758. after 5000 {set ::Module::playpauseDelay 0}
  759. dict set ::Module::playerStatus $::Module::selectedpid state play
  760. }
  761. "stop" {
  762. set ::Module::playpauseDelay 1
  763. device_proc "::Control::setPlayState play $::Module::selectedpid"
  764. set_state "btn_playpause" 0
  765. after 5000 {set ::Module::playpauseDelay 0}
  766. dict set ::Module::playerStatus $::Module::selectedpid state play
  767. }
  768. }
  769. systemCall getPlayerStatus
  770. }
  771. default {LOG "Unknown Multi-State BTN Release Event"}
  772. }
  773. }
  774. }
  775. proc keyboard_handler { return_data } {
  776. switch -- $::Module::currentKeyboard {
  777. "username" {
  778. dict set ::Module::userCredentials username $return_data
  779. set ::Module::currentKeyboard "password"
  780. system_keyboard "HEOS Password" "" "Login Now" "Exit"
  781. }
  782. "password" {
  783. dict set ::Module::userCredentials password $return_data
  784. set ::Module::currentKeyboard ""
  785. device_proc "::Control::auth SignIn [dict get $::Module::userCredentials username] [dict get $::Module::userCredentials password]"
  786. set ::Module::isAuthorizing 0
  787. }
  788. }
  789. }
  790.  
  791. # ------------- Module Procedures -------------
  792. # Handles the Settings Menu UI
  793. # --------------------------------------------
  794.  
  795. proc systemCall {value args} {
  796. switch -- $value {
  797. "heartbeat" {LOG "Starting Heartbeat"; device_proc "::URC::moduleCallback heartbeat"}
  798. "checkConnection" {LOG "Checking for Connection"; return [device_proc "::URC::moduleCallback connection"]}
  799. "getPlayers" {LOG "Getting Heos Players from System"; set ::Module::Players [device_proc "::URC::moduleCallback getPlayers $args"]; LOG "Players\n$::Module::Players"}
  800. "getActivePlayers" {LOG "Getting Active Players from System"; set ::Module::ActivePlayers [device_proc "::URC::moduleCallback getActivePlayers"]; LOG "Active Players:\n$::Module::ActivePlayers"}
  801. "getSources" {LOG "Getting System Source List"; set ::Module::sourceList [device_proc "::URC::moduleCallback getSources $args"]; LOG "$::Module::sourceList"}
  802. "getChanged" {LOG "Getting System AA Dict"; return [device_proc "::URC::moduleCallback getChanged $args"]}
  803. "getAll" {LOG "Getting All Information from System"; }
  804. "getPlayerStatus" {LOG "Getting Player Statuses"
  805. if {$args == ""} {
  806. set ::Module::playerStatus [device_proc "::URC::moduleCallback playerStatus"]
  807. } else {
  808. set response [device_proc "::URC::moduleCallback playerStatus $args"]
  809. dict set ::Module::playerStatus [dict get $response pid] $response
  810. }
  811. LOG "Player Status\n$::Module::playerStatus"
  812. # ::UpdateUI::playerStatus
  813. }
  814. "getAuthorized" {LOG "Checking if Authenticated"; set ::Module::isAuthorized [device_proc "::URC::moduleCallback getAuthorized $args"]; LOG "Authorized? $::Module::isAuthorized"}
  815. "SendData" {LOG "Send Data"; device_proc "::URC::moduleCallback $::Module::selectedpid"}
  816. default {LOG "systemCall Switch Failure"; return -1}
  817. }
  818. }
  819.  
  820.  
  821.  
  822. proc moduleHeartbeat {} {
  823. LOG "Heartbeat for Module"
  824. set ::Module::isConnected [systemCall checkConnection]
  825. if {!$::Module::isConnected} {
  826. ::moduleStartup
  827. }
  828. systemCall heartbeat
  829. systemCall getActivePlayers
  830. systemCall getPlayerStatus
  831. systemCall getPlayers
  832. systemCall getAuthorized
  833. # systemCall SendData
  834. }
  835.  
  836.  
  837.  
  838. namespace eval UpdateUI {
  839. proc Players {oldValue callbackName varName args} {
  840. LOG "Update UI: Players"
  841. upvar 1 $varName newValue
  842. # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
  843. uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
  844. uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
  845.  
  846. ::Update::ActivePlayers
  847. set pids $::Module::totalActivePlayers
  848. set i 1
  849. foreach pid $pids {
  850. while {$i <= $::Clients::playersPerPage} {
  851. if {$i == $::Module::selectedRoom} {::Widgets::NowPlaying::SelectedRoom "[dict get $::Module::ActivePlayers $pid name]"}
  852. set_text "room_${i}_name" "[dict get $::Module::ActivePlayers $pid name]"
  853. incr i
  854. }
  855.  
  856. }
  857. # set changed [::Tools::dictCompare $newValue $oldValue]
  858. # LOG "Player Status Change:\n$changed"
  859. }
  860.  
  861. proc playerStatus {oldValue callbackName varName args} {
  862. LOG "Update UI: Status"
  863. LOG "Player Status is:\n$::Module::playerStatus"
  864. upvar 1 $varName newValue
  865. # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
  866. uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
  867. uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
  868. catch {set pids [dict keys $::Module::ActivePlayers]}
  869. set ::Module::totalActivePlayers [llength $pids]
  870. LOG "Active ID's are $pids"
  871.  
  872.  
  873. }
  874.  
  875.  
  876.  
  877. proc playerStatus {oldValue callbackName varName args} {
  878. LOG "Update UI: Status"
  879. LOG "Player Status is:\n$::Module::playerStatus"
  880. upvar 1 $varName newValue
  881. # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
  882. uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
  883. uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
  884. catch {set pid [dict keys $::Module::ActivePlayers]}
  885. set length [llength $pid]
  886. LOG "PID Values are: $pid"
  887. set i 1
  888. if {[dict exists $::Module::playerStatus $::Module::selectedpid nowplaying song] && $::Module::timedSong != [dict get $::Module::playerStatus $::Module::selectedpid nowplaying song]} {
  889. set ::Module::everyIndicator "2"
  890. set_text "nowplaying_timeplayed" "0:00"
  891. set_text "nowplaying_timeleft" "-0:00"
  892. LOG "Done..."
  893. }
  894. foreach id $pid {
  895. # Need to Edit for Now Playing Page
  896. LOG "Setting up $id"
  897. if {$i == $::Module::selectedRoom} {
  898. catch {set ::Module::CurrentRoom "[dict get $::Module::ActivePlayers $id name]"}
  899. catch {set ::Module::selectedpid $id}
  900. }
  901. LOG "Set $id Name"; catch {set_text "room_${i}_name" "[dict get $::Module::ActivePlayers $id name]"}
  902. LOG "Set $id SongInfo"; catch {set_text "nowplaying_${i}_songinfo" "[dict get $::Module::playerStatus $id nowplaying artist] - [dict get $::Module::playerStatus $id nowplaying song]"}
  903. if {[dict exists $::Module::playerStatus $id nowplaying image_url] && [dict get $::Module::playerStatus $id nowplaying image_url] == ""} {
  904. # If No "Album Artwork" show "ArtMissing" Graphic
  905. catch {set_image "nowplaying_${i}_artwork" "C:\ArtMissing_tkp7.png"}
  906. } elseif {[dict exists $::Module::playerStatus $id nowplaying image_url]} {
  907. # Set "Heos Player" Album Artwork
  908. catch {set_image "nowplaying_${i}_artwork" "[dict get $::Module::playerStatus $id nowplaying image_url]"}
  909. }
  910. if {![dict exists $::Module::playerStatus $id state]} {
  911. LOG "No State Detected, Can't Set Indicator"
  912. } else {
  913. LOG "Indicator Setup"
  914. if {[dict get $::Module::playerStatus $id state] == "play"} {
  915. set_text "nowplaying_${i}_indicator" "b"
  916. if {$id == $::Module::selectedpid} {
  917. if {!$::Module::playpauseDelay} {catch {set_state "btn_playpause" 0}}
  918. if {[dict exists $::Module::prevPlayerStatus $::Module::selectedpid state]} {
  919. if {[dict get $::Module::prevPlayerStatus $::Module::selectedpid state] != "play"} {::UpdateUI::nowPlaying 1}
  920. }
  921. }
  922.  
  923. } else {
  924. catch {set_text "nowplaying_${i}_indicator" "i"}
  925. if {[dict exists $::Module::playerStatus $id nowplaying artist] && [dict get $::Module::playerStatus $id nowplaying artist] == "" && [dict get $::Module::playerStatus $id nowplaying type] == "station"} {
  926. catch {set_text "nowplaying_${i}_songinfo" "[dict get $::Module::playerStatus $id nowplaying station]"}
  927. }
  928. if {$id == $::Module::selectedpid} {
  929. if {!$::Module::playpauseDelay} {catch {set_state "btn_playpause" 1}}
  930. if {[dict exists $::Module::prevPlayerStatus $::Module::selectedpid state]} {
  931. if {[dict get $::Module::prevPlayerStatus $::Module::selectedpid state] == "play"} {
  932. LOG "Status Changed - Stop Progress Indicator"
  933. ::every cancel {::UpdateUI::nowPlayingTime}
  934.  
  935. #HAVE NOT TESTED THIS FULLY
  936. if {[dict exists $::Module::playerStatus $::Module::selectedpid nowplaying duration]} {
  937. dict set ::Module::nowPlayingTime newElapsed [dict get $::Module::playerStatus $::Module::selectedpid nowplaying elapsed]
  938. set pct [expr {(double([dict get $::Module::nowPlayingTime newElapsed])/[dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration])*100}]
  939. set pct [expr {round($pct)}]
  940. ::Tools::songTimeFormat [dict get $::Module::nowPlayingTime newElapsed] [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]
  941. catch {set_text "nowplaying_timeplayed" "[dict get $::Module::nowPlayingTime elapsed]"}
  942. catch {set_text "nowplaying_timeleft" "-[dict get $::Module::nowPlayingTime timeleft]"}
  943. catch {set_progress "nowplaying_progress" $pct}
  944. } else {
  945. set_text "nowplaying_timeplayed" "0:00"
  946. set_text "nowplaying_timeleft" "-0:00"
  947. set_progress "nowplaying_progress" 0
  948. }
  949. }
  950. }
  951. }
  952. }
  953. }
  954. LOG "$id Visibility Setup"
  955. set_visible "room_${i}_name" 1
  956. set_visible "nowplaying_${i}_indicator" 1
  957. set_visible "nowplaying_${i}_songinfo" 1
  958. set_visible "nowplaying_${i}_artwork" 1
  959. set_visible "player${i}" 1
  960. incr i
  961. }
  962. if {$i == 2} {
  963. set_visible "room_2_name" 0
  964. set_visible "btn_2" 0
  965. set_visible "nowplaying_2_indicator" 0
  966. set_visible "nowplaying_2_songinfo" 0
  967. set_visible "nowplaying_2_artwork" 0
  968. set_visible "player2" 0
  969. set_visible "btn_3" 0
  970. set_visible "nowplaying_3_indicator" 0
  971. set_visible "room_3_name" 0
  972. set_visible "nowplaying_3_songinfo" 0
  973. set_visible "nowplaying_3_artwork" 0
  974. set_visible "player3" 0
  975. } elseif {$i == 3} {
  976. set_visible "room_3_name" 0
  977. set_visible "btn_3" 0
  978. set_visible "nowplaying_3_indicator" 0
  979. set_visible "nowplaying_3_songinfo" 0
  980. set_visible "nowplaying_3_artwork" 0
  981. set_visible "player3" 0
  982. } elseif {$i == 1} {
  983. set_visible "player1" 0
  984. set_visible "room_1_name" 0
  985. set_visible "nowplaying_1_songinfo" 0
  986. set_visible "nowplaying_1_artwork" 0
  987. set_visible "nowplaying_1_indicator" 0
  988. }
  989. if {[dict exists $::Module::playerStatus $::Module::selectedpid nowplaying elapsed] && [dict exists $::Module::playerStatus $::Module::selectedpid nowplaying duration]} {
  990. if {[dict get $::Module::playerStatus $::Module::selectedpid nowplaying elapsed] != "" && [dict get $::Module::playerStatus $::Module::selectedpid nowplaying song] != $::Module::timedSong} {
  991. ::UpdateUI::nowPlaying
  992. } elseif {$::Module::timedSongDuration != [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]} {
  993. ::UpdateUI::nowPlaying
  994. }
  995. }
  996. LOG "After Visibility"
  997. catch {set_text "nowplaying_song" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying song]"} {
  998. set_text "nowplaying_song" ""
  999. }
  1000. catch {set_text "nowplaying_artist" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying artist]"} {
  1001. set_text "nowplaying_artist" ""
  1002. }
  1003. if {[dict exists $::Module::playerStatus $id nowplaying type] && [dict get $::Module::playerStatus $id nowplaying type] == "station"} {
  1004. catch {set_text "nowplaying_album" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying station]"}
  1005. } elseif {[dict exists $::Module::playerStatus $id nowplaying type] && [dict get $::Module::playerStatus $id nowplaying type] == "song"} {
  1006. catch {set_text "nowplaying_album" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying album]"}
  1007. }
  1008. catch {set_image "nowplaying_art" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying image_url]"} {
  1009. catch {set_image "C:\ArtMissing_tkp7.png"}
  1010. }
  1011. catch {set_image "nowplaying_art_sm" "[dict get $::Module::playerStatus $::Module::selectedpid nowplaying image_url]"} {
  1012. catch {set_image "C:\ArtMissing_tkp7.png"}
  1013. }
  1014. LOG "Finishing PlayerStatus Setup"
  1015. # set changed [::Tools::dictCompare $newValue $oldValue]
  1016. # LOG "Player Status Change:\n$changed"
  1017. set ::Module::prevPlayerStatus $::Module::playerStatus
  1018. }
  1019.  
  1020. proc groups {args} {
  1021. LOG "Setting Groups"
  1022. set i 1
  1023. foreach player $::Module::Players {
  1024. if {[dict exists $player pid] & [dict get $player pid] != $::Module::selectedpid} {
  1025. set pid [dict get $player pid]
  1026. catch {set_text "link_player_${i}" "[dict get $player name]"}
  1027. if {[dict exists $::Module::playerStatus $pid nowplaying gid]} {
  1028.  
  1029. }
  1030. }
  1031. }
  1032. }
  1033.  
  1034. proc nowPlaying {args} {
  1035. LOG "Updater Timer"
  1036. if {[dict exists $::Module::playerStatus $::Module::selectedpid nowplaying duration] && $::Module::timedSongDuration == [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]} {
  1037. if {$args == "1"} {set ::Module::timedSongDuration -1; ::UpdateUI::nowPlaying; return}
  1038. LOG "|-------- Duration has not Updated!"
  1039. after 1000 {::UpdateUI::nowPlaying}
  1040. } else {
  1041. catch {set ::Module::timedSong [dict get $::Module::playerStatus $::Module::selectedpid nowplaying song]}
  1042. catch {set ::Module::timedSongDuration [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]}
  1043. catch {set ::Module::timedSongElapsed [dict get $::Module::playerStatus $::Module::selectedpid nowplaying elapsed]}
  1044. LOG "Timed Song: $::Module::timedSong"
  1045. catch {dict set ::Module::nowPlayingTime newElapsed [expr {[dict get $::Module::playerStatus $::Module::selectedpid nowplaying elapsed] + 3000}]}
  1046. catch {::every cancel {::UpdateUI::nowPlayingTime}}
  1047. if {[dict exists $::Module::playerStatus $::Module::selectedpid state] && [dict get $::Module::playerStatus $::Module::selectedpid state] != "play"} {
  1048. catch {set pct [expr {(double([dict get $::Module::nowPlayingTime newElapsed])/[dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration])*100}]}
  1049. catch {set pct [expr {round($pct)}]}
  1050. catch {::Tools::songTimeFormat [dict get $::Module::nowPlayingTime newElapsed] [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]}
  1051. catch {set_text "nowplaying_timeplayed" "[dict get $::Module::nowPlayingTime elapsed]"}
  1052. catch {set_text "nowplaying_timeleft" "-[dict get $::Module::nowPlayingTime timeleft]"}
  1053. catch {set_progress "nowplaying_progress" $pct}
  1054. } else {
  1055. ::every 1000 {::UpdateUI::nowPlayingTime}
  1056. }
  1057. set ::Module::everyIndicator 1
  1058. LOG "Every Indicator is $::Module::everyIndicator"
  1059. }
  1060. }
  1061.  
  1062. proc nowPlayingTime {} {
  1063. LOG "Update NowPlaying Time"
  1064. catch {dict set ::Module::nowPlayingTime newElapsed [expr {[dict get $::Module::nowPlayingTime newElapsed] + 1000}]}
  1065. catch {set pct [expr {(double([dict get $::Module::nowPlayingTime newElapsed])/[dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration])*100}]}
  1066. set pct [expr {round($pct)}]
  1067. LOG "Percent: $pct"
  1068. ::Tools::songTimeFormat [dict get $::Module::nowPlayingTime newElapsed] [dict get $::Module::playerStatus $::Module::selectedpid nowplaying duration]
  1069. catch {set_text "nowplaying_timeplayed" "[dict get $::Module::nowPlayingTime elapsed]"}
  1070. catch {set_text "nowplaying_timeleft" "-[dict get $::Module::nowPlayingTime timeleft]"}
  1071. catch {set_progress "nowplaying_progress" $pct}
  1072. }
  1073.  
  1074. proc getCredentials {oldValue callbackName varName args} {
  1075. LOG "Authentication Change Received..."
  1076. upvar 1 $varName newValue
  1077. # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
  1078. uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
  1079. uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
  1080. # if {!$newValue} {LOG "Not Authenticated, Requesting Credentials"
  1081. # if {!$::Module::isAuthorizing} {
  1082. # set ::Module::isAuthorizing 1
  1083. # system_notification "Not Signed In" "You are not currently signed into your Denon HEOS Account." "Sign In"
  1084. # set ::Module::currentKeyboard "username"
  1085. # system_keyboard "HEOS Username" "" "Continue" "Exit"
  1086. # } else {LOG "Currently Authorizing, Waiting for Credentials..."}
  1087. # }
  1088. # set changed [::Tools::dictCompare $newValue $oldValue]
  1089. # LOG "Player Status Change:\n$changed"
  1090. }
  1091.  
  1092. proc CurrentRoom {oldValue callbackName varName args} {
  1093. LOG "Current Room Changed..."
  1094. upvar 1 $varName newValue
  1095. # puts "$varName has Changed:\n$oldValue > changed to > $newValue"
  1096. uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
  1097. uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
  1098. LOG "Old Room: $oldValue\nNew Room: $newValue"
  1099. if {$oldValue != $newValue} {set_text "current_room" $newValue; set_text "current_room2" $newValue; set_text "current_room3" $newValue; set_text "current_room4" $newValue}
  1100. }
  1101.  
  1102. }
  1103.  
  1104.  
  1105.  
  1106. namespace eval Tools {
  1107. proc traceCallback {oldValue callbackName varName args} {
  1108. upvar 1 $varName newValue
  1109. LOG "$varName has Changed:\n$oldValue > changed to > $newValue"
  1110. uplevel [list trace remove variable $varName write [list $callbackName $oldValue $callbackName]]
  1111. uplevel [list trace add variable $varName write [list $callbackName $newValue $callbackName]]
  1112. }
  1113. proc setTrace {varName callbackName} {
  1114. set value ""
  1115. upvar 1 $varName currValue
  1116. if {[info exists currValue]} {set value $currValue}
  1117. uplevel [list trace add variable $varName write [list $callbackName $value $callbackName]]
  1118. LOG "Trace Created"
  1119. }
  1120. proc difflists {l1 l2} {
  1121. set lcsData [::struct::list longestCommonSubsequence $l1 $l2]
  1122. set diffs [::struct::list lcsInvert $lcsData [llength $l1] [llength $l2]]
  1123. set changedKeys ""
  1124. foreach d $diffs {
  1125. set op ""
  1126. lassign $d type i1 i2; lassign $i1 s1 e1; lassign $i2 s2 e2
  1127. LOG "$type: [lrange $l2 {*}$i2] to -> [lrange $l1 {*}$i1]"
  1128. #:[lindex $l1 $e1] -> [lindex $l2 $s2-1]:[lindex $l2 $e2]
  1129. set flatkey [lindex $l1 $s1-1]; lappend op [string map {. " "} $flatkey]
  1130. lappend op [lrange $l1 {*}$i1]
  1131. lappend changedKeys $op
  1132. }
  1133. return $changedKeys
  1134. }
  1135. proc dict_flatten {dictVal} {
  1136. dict for {k v} $dictVal {
  1137. if {[llength $v] % 2 == 0} {
  1138. set dictVal [dict remove $dictVal[set dictVal {}] $k]
  1139. dict for {sk sv} [::Tools::dict_flatten $v] {dict set dictVal ${k}.${sk} $sv}
  1140. }
  1141. }
  1142. return $dictVal
  1143. }
  1144.  
  1145. proc songTimeFormat {elapsed duration} {
  1146. LOG "Time Format Starts"
  1147. set timeleft [expr {$duration - $elapsed}]
  1148. LOG "Elapsed $elapsed Duration $duration Time Left $timeleft"
  1149. if {$timeleft <= 1000} {
  1150. catch {::every cancel {::UpdateUI::nowPlayingTime}}
  1151. set_text "nowplaying_timeplayed" "0:00"
  1152. set_text "nowplaying_timeleft" "-0:00"
  1153. set ::Module::timedSongDuration -1
  1154. after 2000 {::systemCall getPlayerStatus}
  1155. }
  1156. set secs [expr {$elapsed / 1000}]
  1157. set mins [expr {$secs / 60}]
  1158. set secs [expr {$secs - 60*$mins}]
  1159. set Felapsed [format %d:%02d $mins $secs]
  1160. LOG "Elapsed: $Felapsed"
  1161. set secs [expr {$duration / 1000}]
  1162. set mins [expr {$secs / 60}]
  1163. set secs [expr {$secs - 60*$mins}]
  1164. set Fduration [format %d:%02d $mins $secs]
  1165. LOG "Duration: $Fduration"
  1166. set secs [expr {$timeleft / 1000}]
  1167. set mins [expr {$secs / 60}]
  1168. set secs [expr {$secs - 60*$mins}]
  1169. set timeleft [format %d:%02d $mins $secs]
  1170. LOG "Time Left: $timeleft"
  1171. dict set ::Module::nowPlayingTime elapsed $Felapsed
  1172. #dict set ::Module::nowPlayingTime mselapsed $elapsed
  1173. dict set ::Module::nowPlayingTime timeleft $timeleft
  1174. dict set ::Module::nowplayingTime duration $Fduration
  1175. #dict set ::Module::nowPlayingTime msduration $duration
  1176. LOG "Completed Time Format"
  1177. }
  1178.  
  1179. proc dictCompare {newDict oldDict} {
  1180. # Compares Two Dictionaries and Returns with a List
  1181. # with all changed Dictionary Keys.
  1182. # Example Resonse:
  1183. # {key1 key1 key1} {key1 key1 key2} {key2 key1 key1}
  1184. # Get Values using the {*} operator on the response:
  1185. # dict get $dict {*}[lindex $differences 0]
  1186. set differences [::Tools::difflists [::Tools::dict_flatten $newDict] [::Tools::dict_flatten $oldDict]]
  1187. return $differences
  1188. }
  1189.  
  1190.  
  1191. }
  1192. proc every {option args} { # Schedules a Command to happen every XX MS. Can be canceled using ::tools::every cancel $returnedvalue
  1193. LOG "Every Procedure Executed"
  1194. global everyPriv every:UID
  1195. if {[string equal -length [string length $option] $option cancel]} {
  1196. set id {}
  1197. if {[llength $args] == 1 && [string match every#* [lindex $args 0]]} {set id [lindex $args 0]
  1198. } else {set script [eval [list concat] $args]; foreach {key value} [array get everyPriv] {if {[string equal $script [lindex $value 1]]} {set id $key; break}}}
  1199. if {[string length $id]} {after cancel [lindex $everyPriv($id) 2]; unset everyPriv($id)}
  1200. } else {
  1201. set id [format "every#%d" [incr every:UID]]; set script [eval [list concat] $args]; set delay $option
  1202. set aid [after $delay [list every:afterHandler $id]]; set everyPriv($id) [list $delay $script $aid]; return $id
  1203. }
  1204. }
  1205. array set everyPriv {}; set every:UID 0
  1206.  
  1207. proc every:afterHandler {id} {
  1208. global everyPriv
  1209. foreach {delay script oldaid} $everyPriv($id) {}
  1210. set aid [after $delay [info level 0]]; set everyPriv($id) [list $delay $script $aid]
  1211. uplevel #0 $script
  1212. }
  1213. # --------------------------------------------
  1214. changePage "Rooms"
  1215. moduleStartup
  1216.