| Next revision | Previous revision |
| blocks:drivers:hive [2026-10-29 15:32] – created mattias | blocks:drivers:hive [2026-10-29 15:59] (current) – mattias |
|---|
| | **Hive** (driver) | Layers, play list, media, cue lists, synchronisation | REST on port 80 **and** a patch WebSocket on port 9002 (Network Device type: TCP) | Yes. Watches state and reads clocks. | | | **Hive** (driver) | Layers, play list, media, cue lists, synchronisation | REST on port 80 **and** a patch WebSocket on port 9002 (Network Device type: TCP) | Yes. Watches state and reads clocks. | |
| | **HiveMedia** (feed) | The player's media folder by folder, with cached thumbnails | Reads the driver; copies thumbnails over HTTP | Follows the driver's ''mediaRevision'' | | | **HiveMedia** (feed) | The player's media folder by folder, with cached thumbnails | Reads the driver; copies thumbnails over HTTP | Follows the driver's ''mediaRevision'' | |
| | **Hive/TestPanel**, **Hive/MediaBrowser**, **Hive/MediaPanel** (blocks) | A bench panel, a folder browser and a flat media list | Property bindings only | | | | **Hive/TestPanel**, **Hive/MediaBrowser**, **Hive/MediaPanel** (blocks) | A bench panel, a folder browser and a flat media list | Property bindings only | None | |
| |
| Developed and verified against a Hive BeeBlade player on the bench, September–October 2026. Both transports are used at once: the WebSocket carries live state, REST carries commands. | Developed and verified against a Hive BeeBlade player on the bench, September and October 2026. Both transports are used at once: the WebSocket carries live state, REST carries commands. |
| |
| ==== Source files ==== | ==== Source files ==== |
| | ''port'' | 80 | Nectar HTTP port. | | | ''port'' | 80 | Nectar HTTP port. | |
| | ''wsPort'' | 9002 | Patch WebSocket port. | | | ''wsPort'' | 9002 | Patch WebSocket port. | |
| | ''useHttps'' | false | Use ''https://'' and ''wss://'' rather than ''http://'' and ''ws://''. A password-protected player cannot be logged in to over https see //Password protection//. | | | ''useHttps'' | false | Use ''https://'' and ''wss://'' rather than ''http://'' and ''ws://''. A password-protected player cannot be logged in to over https. See //Password protection//. | |
| | ''verbose'' | false | Log every request and sync decision. | | | ''verbose'' | false | Log every request and sync decision. | |
| | ''keepAlive'' | false | True to stop sending ''Connection: close'' with each request. | | | ''keepAlive'' | false | True to stop sending ''Connection: close'' with each request. | |
| | ''syncRate'' | true | Match the master's rate as well, using PLAY SPEED. | | | ''syncRate'' | true | Match the master's rate as well, using PLAY SPEED. | |
| | ''syncStopAction'' | "auto" | A stopped sync source: ''auto'' stops if it rewound to the start and pauses otherwise; ''pause'' and ''stop'' force one. | | | ''syncStopAction'' | "auto" | A stopped sync source: ''auto'' stops if it rewound to the start and pauses otherwise; ''pause'' and ''stop'' force one. | |
| | ''syncDeadDelay'' | 1000 | mS a dead sync source a stopped Blocks timeline must stay dead before it counts as stopped. | | | ''syncDeadDelay'' | 1000 | mS a dead sync source, a stopped Blocks timeline, must stay dead before it counts as stopped. | |
| | ''scrubInterval'' | 200 | mS between seeks while ''time'' is being driven, 0 to disable. | | | ''scrubInterval'' | 200 | mS between seeks while ''time'' is being driven, 0 to disable. | |
| | ''scrubDrift'' | 150 | mS a written position may differ before it counts as a move. | | | ''scrubDrift'' | 150 | mS a written position may differ before it counts as a move. | |
| |
| ^ Property ^ Type ^ Description ^ | ^ Property ^ Type ^ Description ^ |
| | ''connected'' | Boolean, read-only | The player answers, its stream is up and, on a protected player, its login worked so commands are obeyed and state is current. | | | ''connected'' | Boolean, read-only | The player answers, its stream is up and, on a protected player, its login worked, so commands are obeyed and state is current. | |
| | ''deviceName'' | String, read-only | Device name as set on the player, from its System Settings. | | | ''deviceName'' | String, read-only | Device name as set on the player, from its System Settings. | |
| | ''upTime'' | Number, read-only | Seconds Hive's renderer has been running. | | | ''upTime'' | Number, read-only | Seconds Hive's renderer has been running. | |
| | ''playlistStatus'' | String: ''play'', ''pause'', ''stop'' | The play list's state; ''stop'' means Use Playlist is off. Writing it drives the list. | | | ''playlistStatus'' | String: ''play'', ''pause'', ''stop'' | The play list's state; ''stop'' means Use Playlist is off. Writing it drives the list. | |
| | ''playlistEnabled'' | Boolean | Use Playlist, the tick box on Hive's play list page. | | | ''playlistEnabled'' | Boolean | Use Playlist, the tick box on Hive's play list page. | |
| | ''playlistTime'' | TimeFlow, read-only | Position in the whole play list the clock to follow while the play list is on. | | | ''playlistTime'' | TimeFlow, read-only | Position in the whole play list. This is the clock to follow while the play list is on. | |
| | ''playlistRowTime'' | TimeFlow, read-only | Position within the current row. | | | ''playlistRowTime'' | TimeFlow, read-only | Position within the current row. | |
| | ''playlistRow'' | Number, read-only | Current row, 1 for the first, 0 for none. | | | ''playlistRow'' | Number, read-only | Current row, 1 for the first, 0 for none. | |
| | ''playlistTransitionDuration'' | Number, read-only | Cross-fade between rows, mS. | | | ''playlistTransitionDuration'' | Number, read-only | Cross-fade between rows, mS. | |
| | ''playlistTargetLayer'' | Number, read-only | Layer the play list goes out on. Set up in Hive's UI. | | | ''playlistTargetLayer'' | Number, read-only | Layer the play list goes out on. Set up in Hive's UI. | |
| | ''playlistMarker'' | String | Name of the marker last passed, as Hive's UI writes it ''5: Chapte1''. **Writable**: write a marker name to jump there, which is how a panel button can do it. | | | ''playlistMarker'' | String | Name of the marker last passed, as Hive's UI writes it, ''5: Chapte1''. **Writable**: write a marker name to jump there, which is how a panel button can do it. | |
| | ''playlistMarkers[]'' | Indexed property, read-only | One member per marker: ''name'', ''number'', ''label'', ''time'' (mS), ''timeText'' (''m:ss''). | | | ''playlistMarkers[]'' | Indexed property, read-only | One member per marker: ''name'', ''number'', ''label'', ''time'' (mS), ''timeText'' (''m:ss''). | |
| | ''playlistSyncSource'' | String, read-only | Path of the Blocks time property the play list follows, empty if not synchronising. | | | ''playlistSyncSource'' | String, read-only | Path of the Blocks time property the play list follows, empty if not synchronising. | |
| ^ Property ^ Type ^ Description ^ | ^ Property ^ Type ^ Description ^ |
| | ''status'' | String: ''play'', ''pause'', ''stop'' | Playback. ''stop'' means the layer shows black (''0000_BLACK.jpg''). While the play list uses the layer, this is the play list's state. | | | ''status'' | String: ''play'', ''pause'', ''stop'' | Playback. ''stop'' means the layer shows black (''0000_BLACK.jpg''). While the play list uses the layer, this is the play list's state. | |
| | ''playMode'' | Number | Hive's play mode: 7 pauses, 6 is its own Stop, and every other value runs 2 Loop Forward, 4 Play Once Forward, 8 Bounce. | | | ''playMode'' | Number | Hive's play mode: 7 pauses, 6 is its own Stop, and every other value runs: 2 Loop Forward, 4 Play Once Forward, 8 Bounce. | |
| | ''speed'' | Number, 0…1 | 0 halts, 0.5 is normal speed, 1 is ten times. | | | ''speed'' | Number, 0-1 | 0 halts, 0.5 is normal speed, 1 is ten times. | |
| | ''time'' | TimeFlow | Position and rate. **Two-way**: write rate 0/1 to pause and play, or a new position to seek. | | | ''time'' | TimeFlow | Position and rate. **Two-way**: write rate 0/1 to pause and play, or a new position to seek. | |
| | ''media'' | Number | Media by the number Hive shows before the name, within the selected folder. 0 is black, which stops the layer. | | | ''media'' | Number | Media by the number Hive shows before the name, within the selected folder. 0 is black, which stops the layer. | |
| | ''fileName'' | String, read-only | What the layer shows right now. While the play list uses the layer, its current row. | | | ''fileName'' | String, read-only | What the layer shows right now. While the play list uses the layer, its current row. | |
| | ''duration'' | Number, read-only | Length of the selected media, mS. | | | ''duration'' | Number, read-only | Length of the selected media, mS. | |
| | ''intensity'' | Number, 0…1 | Opacity. | | | ''intensity'' | Number, 0-1 | Opacity. | |
| | ''volume'' | Number, 0…1 | 1 is 100 % on Hive's Parameters page. Only for video carrying uncompressed embedded audio. | | | ''volume'' | Number, 0-1 | 1 is 100 % on Hive's Parameters page. Only for video carrying uncompressed embedded audio. | |
| | ''syncSource'' | String, read-only | Path of the Blocks time property this layer follows. | | | ''syncSource'' | String, read-only | Path of the Blocks time property this layer follows. | |
| |
| |
| ^ Property ^ Type ^ Description ^ | ^ Property ^ Type ^ Description ^ |
| | ''scale'' | Number, 0…1 | Zoom. 0.5 is 100 %; below zooms out, above zooms in. | | | ''scale'' | Number, 0-1 | Zoom. 0.5 is 100 %; below zooms out, above zooms in. | |
| | ''framingMode'' | String | How media fits the output: ''LETTERBOX'', ''CROP'', ''STRETCH'', ''MULTI LETTERBOX'', ''CENTERED''. | | | ''framingMode'' | String | How media fits the output: ''LETTERBOX'', ''CROP'', ''STRETCH'', ''MULTI LETTERBOX'', ''CENTERED''. | |
| | ''aspectRatio'' | Number, 0…1 | 0.5 is none; below squeezes horizontally, above vertically. | | | ''aspectRatio'' | Number, 0-1 | 0.5 is none; below squeezes horizontally, above vertically. | |
| | ''positionX'', ''positionY'' | Number, 0…1 | 0.5 is centred. | | | ''positionX'', ''positionY'' | Number, 0-1 | 0.5 is centred. | |
| | ''rotationX'', ''rotationY'', ''rotationZ'' | Number, 0…1 | 0.5 is none. The extremes of ''rotationX'' auto-rotate. | | | ''rotationX'', ''rotationY'', ''rotationZ'' | Number, 0-1 | 0.5 is none. The extremes of ''rotationX'' auto-rotate. | |
| | ''blendMode'' | String | Blend against the layers below: ''ALPHA'', ''ADDITIVE'', ''MULTIPLY'', ''DIFFERENCE'', ''SCREEN'' and 27 more, through to ''GLITCH''. | | | ''blendMode'' | String | Blend against the layers below: ''ALPHA'', ''ADDITIVE'', ''MULTIPLY'', ''DIFFERENCE'', ''SCREEN'' and 27 more, through to ''GLITCH''. | |
| | ''red'', ''green'', ''blue'' | Number, 0…1 | 0.5 leaves the channel at 100 %. | | | ''red'', ''green'', ''blue'' | Number, 0-1 | 0.5 leaves the channel at 100 %. | |
| | ''hue'' | Number, 0…1 | Across the full 360 degrees. | | | ''hue'' | Number, 0-1 | Across the full 360 degrees. | |
| | ''saturation'', ''contrast'' | Number, 0…1 | 0.5 leaves them alone. | | | ''saturation'', ''contrast'' | Number, 0-1 | 0.5 leaves them alone. | |
| | ''lut'' | Number | LUT from the player's LUTS folder, 0 for none. | | | ''lut'' | Number | LUT from the player's LUTS folder, 0 for none. | |
| | ''strobe'' | Number, 0…1 | Below 0.5 is on/off strobe slow to fast, above is punch strobe. | | | ''strobe'' | Number, 0-1 | Below 0.5 is on/off strobe slow to fast, above is punch strobe. | |
| | ''inFrame'', ''outFrame'' | Number | Frame numbers playback starts from and ends or loops at. | | | ''inFrame'', ''outFrame'' | Number | Frame numbers playback starts from and ends or loops at. | |
| | ''fx1Select'', ''fx2Select'' | Number | Effect by number, 0 for none. Hive documents 1 Old TV, 2 Sepia, 3 Feedback, 4 Blur and on to 15. | | | ''fx1Select'', ''fx2Select'' | Number | Effect by number, 0 for none. Hive documents 1 Old TV, 2 Sepia, 3 Feedback, 4 Blur and on to 15. | |
| | ''fx1Opacity'', ''fx2Opacity'' | Number, 0…1 | Effect opacity. | | | ''fx1Opacity'', ''fx2Opacity'' | Number, 0-1 | Effect opacity. | |
| | ''transitionDuration'' | Number, mS | Cross-fade duration. | | | ''transitionDuration'' | Number, mS | Cross-fade duration. | |
| | ''transitionMode'' | String | Cross-fade mode, named as ''blendMode''. | | | ''transitionMode'' | String | Cross-fade mode, named as ''blendMode''. | |
| |
| ^ Callable ^ Description ^ | ^ Callable ^ Description ^ |
| | ''selectMedia(folder, media, then)'' | Select folder and media together one swap on a playing layer, where setting ''folder'' and ''media'' separately makes two. | | | ''selectMedia(folder, media, then)'' | Select folder and media together, so a playing layer swaps once where setting ''folder'' and ''media'' separately makes it swap twice. | |
| | ''setTimePosition(time, then)'' | Jump to a position in the media on the layer. | | | ''setTimePosition(time, then)'' | Jump to a position in the media on the layer. | |
| | ''playFrom(time)'' | Play the selected media from a given position. | | | ''playFrom(time)'' | Play the selected media from a given position. | |
| | ''toggle()'' | Run the layer if it is stopped or paused, pause it if it is running. | | | ''toggle()'' | Run the layer if it is stopped or paused, pause it if it is running. | |
| | ''setFxParam(effect, param, value)'' | Set one of an effect's 16 parameters. | | | ''setFxParam(effect, param, value)'' | Set one of an effect's 16 parameters. | |
| | ''setFxParams(effect, p1 … p16)'' | Set any of them together in one call; a parameter left out keeps its value. | | | ''setFxParams(effect, p1 to p16)'' | Set any of them together in one call; a parameter left out keeps its value. | |
| | ''readFxParam(effect, param)'' | Read one parameter into the log. | | | ''readFxParam(effect, param)'' | Read one parameter into the log. | |
| | ''logCues()'' | Log this layer's cue list, with each cue's trigger time and length. | | | ''logCues()'' | Log this layer's cue list, with each cue's trigger time and length. | |
| The player's files are published twice over, from one catalogue read: | The player's files are published twice over, from one catalogue read: |
| |
| * ''mediaList[]'' an indexed property, folder then number order, for a replicated list. | * ''mediaList[]'', an indexed property, folder then number order, for a replicated list. |
| * ''media.<key>'' the same rows keyed ''ISE_2026_0007'' or ''root_0001'': the folder name, or ''root'', then the 4-digit number. **A key survives folders coming and going**, where a folder //number// shifts, so a binding made through a key keeps working. | * ''media.<key>'', the same rows keyed ''ISE_2026_0007'' or ''root_0001'': the folder name, or ''root'', then the 4-digit number. **A key survives folders coming and going**, where a folder //number// shifts, so a binding made through a key keeps working. |
| |
| ^ Property ^ Type ^ Description ^ | ^ Property ^ Type ^ Description ^ |
| | ''onLayers'' | String, read-only | The layers showing it, e.g. ''1,2''. Paused counts; stopped does not. | | | ''onLayers'' | String, read-only | The layers showing it, e.g. ''1,2''. Paused counts; stopped does not. | |
| | ''layer'' | Number | The layer showing it, lowest if several, 0 if none. Write a layer number to play it there, 0 to stop it wherever it plays. | | | ''layer'' | Number | The layer showing it, lowest if several, 0 if none. Write a layer number to play it there, 0 to stop it wherever it plays. | |
| | ''onLayer1'' … ''onLayer8'' | Boolean | One per layer: true while that layer shows the file. Write true to play it there, false to stop **just that layer**, leaving the others alone. | | | ''onLayer1'' to ''onLayer8'' | Boolean | One per layer: true while that layer shows the file. Write true to play it there, false to stop **just that layer**, leaving the others alone. | |
| |
| **Black has a member of its own**, ''media.root_0000'' (''0000_BLACK.jpg''), which ''mediaList'' leaves out. Its ''onLayerN'' reads true while that layer is stopped, writing true stops the layer, and writing false plays the layer's selection again so a panel can put a layer to black from the same list it picks media from. | **Black has a member of its own**, ''media.root_0000'' (''0000_BLACK.jpg''), which ''mediaList'' leaves out. Its ''onLayerN'' reads true while that layer is stopped, writing true stops the layer, and writing false plays the layer's selection again, so a panel can put a layer to black from the same list it picks media from. |
| |
| The catalogue is re-read at startup, on ''refresh()'', when ''refreshMedia()'' finishes, when Hive's UI rewrites its media list (an upload or a delete, within about a second), and every ''mediaCheckInterval'' for changes nothing announces. | The catalogue is re-read at startup, on ''refresh()'', when ''refreshMedia()'' finishes, when Hive's UI rewrites its media list (an upload or a delete, within about a second), and every ''mediaCheckInterval'' for changes nothing announces. |
| ^ Callable ^ Description ^ | ^ Callable ^ Description ^ |
| | ''refresh()'' | Re-probe the player and re-read every published value. | | | ''refresh()'' | Re-probe the player and re-read every published value. | |
| | ''refreshMedia()'' | Refresh the media list, as REFRESH LIST on Hive's Media tab does. **Returns only when it is done** the refresh, any cache rebuild still running and a re-read of the list so a Task's next step sees the new files. Fails, stopping a Task set to abort on error, if the player refuses it or a rebuild takes over five minutes. | | | ''refreshMedia()'' | Refresh the media list, as REFRESH LIST on Hive's Media tab does. **Returns only when it is done**: the refresh, any cache rebuild still running and a re-read of the list. A Task's next step therefore sees the new files. Fails, stopping a Task set to abort on error, if the player refuses it or a rebuild takes over five minutes. | |
| | ''stopAll()'' | Stop every published layer. | | | ''stopAll()'' | Stop every published layer. | |
| | ''resendFromQueen()'' | Ask the queen to rebroadcast its parameters to every worker. | | | ''resendFromQueen()'' | Ask the queen to rebroadcast its parameters to every worker. | |
| ==== system.logging ==== | ==== system.logging ==== |
| |
| Eleven read-only callables that print what the player says into the Blocks log: ''logNames'', ''logMedia'', ''logPlaylist'', ''logSystemInfo'', ''logStorage'', ''logNetwork'', ''logPlaybackLog'', ''logHiveLog'', ''logNectarLog'', ''logDeviceLog'' and ''logSyncStatus'' (the BeeSync and PTP summary how well a cluster is locked together). | Eleven read-only callables that print what the player says into the Blocks log: ''logNames'', ''logMedia'', ''logPlaylist'', ''logSystemInfo'', ''logStorage'', ''logNetwork'', ''logPlaybackLog'', ''logHiveLog'', ''logNectarLog'', ''logDeviceLog'' and ''logSyncStatus'', the BeeSync and PTP summary, which says how well a cluster is locked together. |
| |
| ''system.logging.lastLog'' holds what the last of them printed, so a panel or a Task can show it without reading the log. | ''system.logging.lastLog'' holds what the last of them printed, so a panel or a Task can show it without reading the log. |
| ==== patch ==== | ==== patch ==== |
| |
| Generic access for anything the driver does not publish the vendor states the path list is not exhaustive, so this is the escape hatch: | Generic access for anything the driver does not publish. The vendor states the path list is not exhaustive, so this is the escape hatch: |
| |
| ^ Callable ^ Description ^ | ^ Callable ^ Description ^ |
| **The player is authoritative.** State is watched over the WebSocket and applied as it arrives, so a change made in Hive's own UI shows up in Blocks without polling. | **The player is authoritative.** State is watched over the WebSocket and applied as it arrives, so a change made in Hive's own UI shows up in Blocks without polling. |
| |
| **Clocks are read, not watched.** A watch on a running clock pushes a message every frame measured at 50 messages/s per clock path on the bench player, 201 messages/s and 25.6 kB/s in total with a play list running. The API offers no throttle or deadband, so instead the driver reads a running layer's clock every ''timeReadInterval'' (5 s) and the play list's every ''playlistTimeReadInterval'' (1 s), with one extra read just after a row should end. A **still** clock is watched, since it sends nothing until it moves. The trade-off: a pause made in Hive's UI shows within about a second, a seek on a running layer within five. | **Clocks are read, not watched.** A watch on a running clock pushes a message every frame: measured at 50 messages/s per clock path on the bench player, 201 messages/s and 25.6 kB/s in total with a play list running. The API offers no throttle or deadband, so instead the driver reads a running layer's clock every ''timeReadInterval'' (5 s) and the play list's every ''playlistTimeReadInterval'' (1 s), with one extra read just after a row should end. A **still** clock is watched, since it sends nothing until it moves. The trade-off: a pause made in Hive's UI shows within about a second, a seek on a running layer within five. |
| |
| **A still is published still.** Hive runs a picture or a ''.url'' page as a one-second clip on a loop, so its clock wraps every second. The driver publishes ''time'' at 0 with rate 0 for those, as Hive's own progress bar does, while ''status'' stays ''play''. | **A still is published still.** Hive runs a picture or a ''.url'' page as a one-second clip on a loop, so its clock wraps every second. The driver publishes ''time'' at 0 with rate 0 for those, as Hive's own progress bar does, while ''status'' stays ''play''. |
| ==== Synchronisation ==== | ==== Synchronisation ==== |
| |
| ''startSync(path)'' on a layer, or ''playlistStartSync(path)'', makes a Blocks TimeFlow property the master. The driver then follows position and, with ''syncRate'', rate. A drift beyond ''syncDrift'' is corrected; a change beyond ''syncJump'' counts as a seek. A dead master a stopped Blocks timeline is treated per ''syncStopAction''. | ''startSync(path)'' on a layer, or ''playlistStartSync(path)'', makes a Blocks TimeFlow property the master. The driver then follows position and, with ''syncRate'', rate. A drift beyond ''syncDrift'' is corrected; a change beyond ''syncJump'' counts as a seek. A dead master, a stopped Blocks timeline, is treated per ''syncStopAction''. |
| |
| Hive has ratecontrol - PLAY SPEED, 0…1, where 0.5 is normal so it can follow a master's rate rather than chase its position. | Hive has a usable rate control, PLAY SPEED, 0-1, where 0.5 is normal, so the driver can follow a master's rate rather than chase its position. |
| |
| ==== Password protection ==== | ==== Password protection ==== |
| |
| Turn protection on in Hive's UI and **every** route each ''/api'' call, the thumbnails and the UI itself answers a request without a session with a redirect to the login page. Give the driver ''username'' and ''password'' in the options and it logs in by itself; the session lasts an hour, and the next request after that logs in again. | Turn protection on in Hive's UI and **every** route, each ''/api'' call, the thumbnails and the UI itself, answers a request without a session with a redirect to the login page. Give the driver ''username'' and ''password'' in the options and it logs in by itself; the session lasts an hour, and the next request after that logs in again. |
| |
| **Important:** the login is sent over the device's own TCP connection, not through the HTTP client, because Hive sets the session cookie on a redirect that the client follows and discards. One consequence: a protected player cannot be logged in to over **https**, since that raw connection speaks plain HTTP only. | **Important:** the login is sent over the device's own TCP connection, not through the HTTP client, because Hive sets the session cookie on a redirect that the client follows and discards. One consequence: a protected player cannot be logged in to over **https**, since that raw connection speaks plain HTTP only. |
| **Port 9002 has no authentication of any kind**, and password protection does not cover it. Anything that can reach the player there can read and write any patch path, and stop the show. Keep it on a trusted network. | **Port 9002 has no authentication of any kind**, and password protection does not cover it. Anything that can reach the player there can read and write any patch path, and stop the show. Keep it on a trusted network. |
| |
| ===== 8. Blocks and the media feed ===== | ===== 8. HiveMedia - the media feed ===== |
| |
| ''script/feed/HiveMedia.ts'' publishes, for every network device running this driver: | ''script/feed/HiveMedia.ts'' publishes a Hive player's media **folder by folder**, with a cached copy of each file's thumbnail. It is what ''Hive/MediaBrowser'' browses. |
| |
| ^ Feed instance ^ Rows ^ | The driver alone covers a flat list: ''mediaList'' replicates straight into a Scroller, which is what ''Hive/MediaPanel'' does. The feed exists for the two things a driver cannot do: |
| | ''<Device>_folders'' | One per media folder holding files | | |
| | ''<Device>_folderPages'' | The same rows again, for a second list on the same Spot | | * **A list per folder.** ''mediaList'' is one list of every file, and Blocks will not nest an indexed property inside an aggregate, so a driver cannot hang a list off each folder. |
| | * **Thumbnails.** A driver's media rows carry no picture. The feed copies each one into ''public/'', so Spots render it from Blocks and never reach the player. |
| | |
| | ==== What it publishes ==== |
| | |
| | For **every** network device running the Hive driver, named after the device: |
| | |
| | ^ Instance ^ Rows ^ |
| | | ''<Device>_folders'' | One per media folder holding files - for a row of folder buttons | |
| | | ''<Device>_folderPages'' | The same rows again - for a Book with one page per folder | |
| | ''<Device>_root'' | The files in the media root | | | ''<Device>_root'' | The files in the media root | |
| | ''<Device>_folder_<name>'' | The files in that folder | | | ''<Device>_folder_<name>'' | The files in that folder, e.g. ''HivePlayer_folder_ISE_2026'' | |
| | ''<Device>_folderAt_<n>'' | The files of the folder at row //n//, so a Book page can pick its own feed | | | ''<Device>_folderAt_<n>'' | The files of the folder at row //n// of ''_folders'', 0 first - past the end it serves the first folder's | |
| |
| Each file row carries ''subPath'', the path of its live member in the driver's ''media'' aggregate, so a row's lamp and buttons bind through it ''Network.[Relative.subPath].playing'', ''.onLayer1'' and so on. | **Why ''_folderPages'' is a twin of ''_folders'':** a Spot keeps one copy of a feed instance, and only the list that subscribed first is rebuilt when the feed refreshes. A second list on the same instance keeps the rows it first got, and never shows a folder added later. So the folder buttons take one instance and the Book takes the other. |
| | |
| | **Why the position feeds exist:** a list's ''Script.feed'' path is resolved once, as it renders, with brackets filled from **parameters only** - a ''Relative'' there is never looked up, so a page cannot name its own feed. A Book renders a page as it is navigated to, so each page's Scroller picks its files from the same spot parameter that turned the page: |
| | |
| | <code> |
| | Script.feed.HiveMedia.['HivePlayer_folderAt_' + folderPage] |
| | </code> |
| | |
| | ==== Folder rows ==== |
| | |
| | ^ Field ^ Description ^ |
| | | ''feed'' | Name of this folder's own feed instance. | |
| | | ''folderName'' | Folder name, empty for the media root. | |
| | | ''label'' | Caption: the folder name, or ''Root'' for the media root. | |
| | | ''fileCount'' | Files in the folder, ''0000_BLACK.jpg'' not counted. | |
| | | ''index'' | Position in this list, 0 for the first - the page number for a Book replicated from it. | |
| | | ''folder'' | Hive's folder number. Shifts as folders come and go. | |
| | |
| | ==== File rows ==== |
| | |
| | ^ Field ^ Description ^ |
| | | ''key'' | Key in the driver's ''media'' aggregate, e.g. ''ISE_2026_0007''. | |
| | | ''path'' / ''subPath'' | Path of the file's live member in the driver, with and without its root. | |
| | | ''mediaName'', ''folderName'', ''kind'', ''media'' | The file, its folder, ''video''/''image''/''other'', and the number a layer selects it by. | |
| | | ''lengthText'' | Length as ''m:ss'', empty until the driver has read it. | |
| | | ''thumbnail'' | Served path of the cached thumbnail, empty until it has been copied. | |
| | |
| | **Feed rows are snapshots; what a file is doing is not.** That is what ''subPath'' is for - a row's lamp and buttons bind through it to the live member in the driver: |
| | |
| | <code> |
| | Indicator propPath = Network.[Relative.subPath].playing |
| | Button propPath = Network.[Relative.subPath].onLayer1 Toggle true / false |
| | </code> |
| | |
| | Each folder's list starts with ''0000_BLACK.jpg'', so a panel can put a layer to black from the page it is already on. ''fileCount'' leaves it out. |
| | |
| | ==== Thumbnails ==== |
| | |
| | Hive serves each file's thumbnail at ''http://<player>/Thumbs/<folder>/<name>.jpg''. The feed keeps a copy at ''/public/hivethumbs/<device>/<key>_<etag>.jpg'' - that path **is** the URL a row hands out, and the ETag in the name means a replaced thumbnail gets a new path rather than a stale cached one. |
| | |
| | A pass runs whenever the driver's ''mediaRevision'' moves, and on ''refreshThumbnails()'': |
| | |
| | * one ''HEAD'' per file, one at a time; |
| | * an unchanged ETag is kept, an existing copy is adopted, anything else is downloaded; |
| | * after a complete pass, copies of files that have gone are deleted, as is anything else left in the device's folder. |
| | |
| | On a **password-protected** player the feed sends the driver's session with each request. If it meets the login page anyway - the session has just expired, or the driver has not logged in yet - it tries the pass again after 40 seconds, three times, before saying so once and keeping the copies it has. |
| | |
| | ==== Several players ==== |
| | |
| | Discovery is automatic: the feed asks every network device whether it runs the Hive driver, and serves each one it finds under its own device name, with its own thumbnail folder. It looks at load, then again after 5 and 30 seconds, so a driver that is slow to come up is still picked up. |
| | |
| | A player added after that is picked up by ''discover()''. A **disabled** device is served as empty lists rather than disappearing. |
| | |
| | **A block, however, names its player.** ''Hive/MediaBrowser'' carries the device name in its feed path and in its two status lines, so several players means either one copy of the block per player, or a block that takes the device from a spot parameter. |
| | |
| | ==== Callables ==== |
| | |
| | ^ Callable ^ Description ^ |
| | | ''discover()'' | Look for Hive devices again, and publish feeds for any not served yet. | |
| | | ''refreshThumbnails()'' | Check every thumbnail against its player again, downloading any that changed. | |
| | | ''reInitialize()'' | Re-initialise the feed - the way to drop feeds of folders that no longer exist. | |
| |
| **Thumbnails are copied, not linked.** The feed caches each one under ''/public/hivethumbs/<device>/'' with the player's ETag in the file name, and a row's ''thumbnail'' is that served path so Spots never need to reach the player. On a protected player the feed borrows the driver's session to fetch them. | ===== 9. The blocks ===== |
| |
| The three blocks are ready to use and are described in their own ''README.md'': | Three blocks are ready to use, each described in its own ''README.md'': |
| |
| * **Hive/TestPanel** every layer control, the play list and its markers, for the bench. | ^ Block ^ What it is ^ Needs the feed ^ |
| * **Hive/MediaBrowser** the media folder by folder, with thumbnails, one page per folder. | | ''Hive/TestPanel'' | Every layer control, the play list and its markers, for the bench | No | |
| * **Hive/MediaPanel** the whole media list, flat. | | ''Hive/MediaBrowser'' | The media folder by folder, with thumbnails, a page per folder | **Yes** | |
| | | ''Hive/MediaPanel'' | The whole media list, flat, replicated from ''mediaList'' | No | |
| |
| ===== 9. UI-derived paths ===== | ===== 10. UI-derived paths ===== |
| |
| Hive's documented path list is explicitly not exhaustive, and some useful state lives on paths found in the player's own web UI rather than in the catalogue. Those are used only while ''uiDerivedPaths'' is true, which is the default. | Hive's documented path list is explicitly not exhaustive, and some useful state lives on paths found in the player's own web UI rather than in the catalogue. Those are used only while ''uiDerivedPaths'' is true, which is the default. |