Compare commits

...

319 Commits

Author SHA1 Message Date
Luke Bermingham cb741add4f
Readme to new repo: https://github.com/EpicGamesExt/PixelStreamingInfrastructure
More details can be found here: https://forums.unrealengine.com/t/migrating-optional-epic-games-git-repositories-to-new-github-organization/1718666

Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2024-02-29 15:02:21 +10:00
mcottontensor 88d82e1112
Merge pull request #492 from mcottontensor/setting_option_fix
Setting option fix
2024-02-20 15:20:32 +11:00
mcottontensor caddb00ed3
Merge branch 'master' into setting_option_fix 2024-02-20 15:18:34 +11:00
Matthew Cotton f0fd72768c
Fixed incorrect settings setup with SettingOption 2024-02-20 15:13:29 +11:00
Matthew Cotton 7cbe5227dd
Merge remote-tracking branch 'upstream/master' 2024-02-20 15:05:43 +11:00
mcottontensor 672397e6a8
Update README.md
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2024-02-20 14:58:20 +11:00
David MacPherson fa75e9d8c5
Implemented a bug fix to remove the setupWebRTCtcpDetectEvent from the event emitter (#490) 2024-02-19 13:23:01 +10:00
David MacPherson 4969548535
Add TCP+Relay detection event (#485)
* Change the label for the remote candidate
* Added the following missing WebRTC stats id,timestamp,type,lastPacketReceivedTimestamp,lastPacketSentTimestamp,priority,remoteCandidateId,transportId and writable
* Added relayProtocol and transport ID to the candidate Stat
* Refactored the candidate pair to be an array as many pairs are generated
* Implemeneted a WebRtcTCPRelayDetectedEvent that is fired when WebRTC transport is TCP AND we are using a relay candidate (e.g. a TURN server is being used).
* Refactored the stats panel to use the `getActiveCandidatePair()`
2024-02-19 10:52:32 +10:00
Luke Bermingham e91781b6fa
Create angular/readme.md
Added a readme.md for angular implementations that links to community provided examples.

Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2024-02-16 14:04:17 +10:00
Bryan eea0faff60
Fix typo readme (#488)
Signed-off-by: Bryan <69793084+Ahhj93@users.noreply.github.com>
2024-02-15 11:49:07 +10:00
William Belcher 664d40b801
Update TouchStart and TouchMove handlers to force a touch force of 1 if the touch.force member is 0 (#486) 2024-02-14 14:02:48 +10:00
mcottontensor 5cf2735c5f
Update README.md
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2024-01-24 15:52:58 +11:00
mcottontensor 2544b717ae
Merge pull request #476 from EpicGames/UE5.4
Syncing master with UE5.4
2024-01-24 15:34:31 +11:00
mcottontensor b2cdcf10f3
Update RELEASE_VERSION
Updating main minor version for the release.

Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2024-01-24 15:31:20 +11:00
Matthew Cotton e44a9ff734
Bumping minor version and library reference. 2024-01-24 15:29:54 +11:00
mcottontensor 8734e5bb56
Update package.json
Bumping minor version

Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2024-01-24 15:26:57 +11:00
mcottontensor f272cefb93
Merge pull request #475 from EpicGames/master
Syncing with master
2024-01-24 15:22:53 +11:00
mcottontensor d61b989bff
Merge pull request #453 from kroecks/master
Fix for build-all script dependencies
2024-01-24 15:20:35 +11:00
mcottontensor 4aded6b752
Merge branch 'master' into master 2024-01-24 15:19:31 +11:00
mcottontensor dee225c03a
Merge pull request #464 from EpicGames/dependabot/npm_and_yarn/Frontend/implementations/react/follow-redirects-1.15.4
Bump follow-redirects from 1.15.2 to 1.15.4 in /Frontend/implementations/react
2024-01-24 15:18:42 +11:00
mcottontensor ce2a493114
Merge branch 'master' into dependabot/npm_and_yarn/Frontend/implementations/react/follow-redirects-1.15.4 2024-01-24 15:16:47 +11:00
mcottontensor 44367dfa8a
Merge pull request #465 from EpicGames/dependabot/npm_and_yarn/Frontend/implementations/typescript/follow-redirects-1.15.4
Bump follow-redirects from 1.15.2 to 1.15.4 in /Frontend/implementations/typescript
2024-01-24 15:16:22 +11:00
mcottontensor 23e562dfdf
Merge branch 'master' into master 2024-01-24 15:15:10 +11:00
mcottontensor 20987e006e
Merge branch 'master' into dependabot/npm_and_yarn/Frontend/implementations/react/follow-redirects-1.15.4 2024-01-24 15:14:37 +11:00
mcottontensor a0f9b34dde
Merge branch 'master' into dependabot/npm_and_yarn/Frontend/implementations/typescript/follow-redirects-1.15.4 2024-01-24 15:13:44 +11:00
Denis Phoenix f4380ab2c0
Update copyright/licence year (#474)
Update licence and copyright year in all relevant files.
2024-01-23 14:30:41 +10:00
Matthew Cotton 556ecc5df3
Allowing signalling test action to be manually triggered 2024-01-22 12:17:59 +11:00
Matthew Cotton cb58c2c068
adding a signalling test action to master so it can be run in the signalling_tester branch 2024-01-22 12:16:42 +11:00
William Belcher 6d59c44126
Export NO_SUDO so it's available for use in launched processes (#472) 2024-01-15 13:29:41 +10:00
William Belcher 04c2bc5e70
Move settings order so that they are parsed default settings -> intitial settings -> url params (#471) 2024-01-15 11:33:46 +10:00
dependabot[bot] d355d6685a
Bump follow-redirects in /Frontend/implementations/typescript
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:12:16 +00:00
dependabot[bot] 1b00462e1c
Bump follow-redirects in /Frontend/implementations/react
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 13:38:27 +00:00
mcottontensor ce299ebd37
Merge pull request #452 from Belchy06/master
Add support for non-latin characters
2023-12-15 10:17:09 +11:00
Ken Roecks 7315292918
Merge branch 'master' into master 2023-12-13 18:03:03 -08:00
William Belcher 8b838f48fe
Add support for non-latin characters 2023-12-13 13:22:45 +10:00
mcottontensor 87b9c9aaed
Merge pull request #451 from Belchy06/master
Fix warnings about GamepadButtonReleased
2023-12-13 11:02:50 +11:00
William Belcher 898504d3a6
Fix warnings about GamepadButtonReleased 2023-12-13 09:53:49 +10:00
Kenneth Roecks 6ac47fffcd Fix for build-all script dependencies 2023-12-12 13:54:01 -08:00
William Belcher 606298d40d Ensure gamepad disconnect is triggered whenever a user navigates away from the player page 2023-12-12 20:26:24 +10:00
mcottontensor d8a4e118a6
Merge pull request #448 from alien299/alien299-ContributingGuidelines-patch01-Closes-#445
Update CONTRIBUTING.md - Added Verified Commits as a Requirement - Closes#445
2023-12-11 09:12:30 +11:00
mcottontensor eb7d4c6262
Merge branch 'master' into alien299-ContributingGuidelines-patch01-Closes-#445 2023-12-11 09:12:10 +11:00
mcottontensor 4ebc522437
Merge pull request #443 from mcottontensor/stale_legacy_timer
Small signalling server fixes.
2023-12-11 09:10:12 +11:00
mcottontensor c6ad00f2ca
Merge branch 'master' into stale_legacy_timer 2023-12-11 09:09:56 +11:00
mcottontensor c05f3b18ce
Merge pull request #441 from alien299/alien299-WebsocketErrorPatch
Update cirrus.js - More informative error messages - Fixes EpicGames/PixelStreamingInfrastructure#184
2023-12-11 09:09:18 +11:00
Patrik Nagy db6273513a
Update CONTRIBUTING.md - Added Verified Commits as a Requirement
Signed-off-by: Patrik Nagy <52012944+alien299@users.noreply.github.com>
2023-12-07 18:30:26 +01:00
pnagy e5f2ff8dd1
Merge branch 'alien299-WebsocketErrorPatch' of https://github.com/alien299/PixelStreamingInfrastructure into alien299-WebsocketErrorPatch 2023-11-30 10:07:54 +01:00
pnagy 2c5628124a
Update cirrus.js - More informative error messages
Fixed an Issue regarding the Websocket disconnect error messages. I made them more informative so they can assist with debugging.
[Issue #184](https://github.com/EpicGames/PixelStreamingInfrastructure/issues/184)
2023-11-30 10:05:45 +01:00
Matthew Cotton 05821c7656
Fixing small issue where if a streamer disconnected before identifying the signalling server would create a legacy name that would be left behind.
Fixing some logging calls that would not properly be handled by the logging system.
2023-11-29 12:02:02 +11:00
pnagy 92de29dbc9 Update cirrus.js - More informative error messages
Fixed an Issue regarding the Websocket disconnect error messages. I made them more informative so they can assist with debugging.
[Issue #184](https://github.com/EpicGames/PixelStreamingInfrastructure/issues/184)
2023-11-28 12:38:50 +01:00
mcottontensor 28e7c0c1f3
Merge pull request #432 from mcottontensor/fix_late_streamer_id
Fixing left behind streamer IDs when they're late to ID themselves.
2023-11-23 11:24:00 +11:00
mcottontensor 649d6b2dcb
Merge branch 'master' into fix_late_streamer_id 2023-11-22 16:32:23 +11:00
mcottontensor a3b76a3acc
Merge pull request #438 from mcottontensor/player_config_streamerid
Fixing handling of StreamerId in the initial config settings.
2023-11-22 16:32:05 +11:00
mcottontensor 5a734f2204
Merge branch 'master' into fix_late_streamer_id 2023-11-22 13:08:54 +11:00
Matthew Cotton 6fe29baa95
Fixing small typos 2023-11-22 13:07:30 +11:00
Matthew Cotton 5e71c6ff97
Fixing default behaviour when joining with one streamer. 2023-11-22 12:52:26 +11:00
Matthew Cotton 6462905b76
Adding support to set the streamer id in the pixel streaming initial settings. Fixes #436 2023-11-22 11:55:24 +11:00
Even Stensberg 7eba788faa
chore: remove explicit /dist dir in Webpack as it is implicit (#434)
Signed-off-by: Even Stensberg <evenstensberg@gmail.com>
2023-11-22 09:13:59 +10:00
Matthew Cotton e9b860428d
Bumping protocol docs version number 2023-11-22 10:08:49 +11:00
Matthew Cotton 4fd2948d99
Adding a little more context to the changed id message documentation. 2023-11-22 10:06:00 +11:00
Matthew Cotton 78640671d4
Adding the id change message to the protocol docs for signalling. 2023-11-22 10:00:29 +11:00
timbotimbo cfbe5a0f86
Fix formatting in bug_report template. (#437)
Signed-off-by: timbotimbo <timbotimbo@users.noreply.github.com>
2023-11-22 08:33:58 +10:00
Matthew Cotton ae4b5b7a6d
Updating the settings UI when streamer id changes. 2023-11-21 13:26:40 +11:00
mcottontensor a8de900166
Merge branch 'master' into fix_late_streamer_id 2023-11-21 11:54:10 +11:00
mcottontensor 12a08e1624
Merge pull request #431 from mcottontensor/sfu_docker
Dockerfile for SFU
2023-11-21 11:53:53 +11:00
Matthew Cotton a9b4f31a7d
Adding some information on running the docker image to the new documentation. 2023-11-21 11:53:32 +11:00
mcottontensor d8ac1b62d8
Merge branch 'master' into sfu_docker 2023-11-21 11:44:01 +11:00
mcottontensor e66dacf750
Merge pull request #429 from mcottontensor/new_docs
New docs
2023-11-21 11:43:42 +11:00
Matthew Cotton 8080d2aebb
Adding in new streamer id changed event/message so that the frontend can keep track of its streamer (mostly for reconnecting). 2023-11-21 11:18:28 +11:00
Matthew Cotton 4fd080675e
Adding container build to existing container action. Also added container reference on main README. 2023-11-21 09:55:19 +11:00
Matthew Cotton bbba5e0a67
Small fixes from reviews 2023-11-20 16:19:29 +11:00
Matthew Cotton c37095d229
Fixing the issue when a streamer was late to ID itself, it would leave behind the legacy id 2023-11-20 15:55:38 +11:00
Matthew Cotton f94039dac8
Fixing up some linux scripts. Cleaning up SFU dockerfile 2023-11-20 04:09:56 +00:00
Matthew Cotton 76bdc5e683
Attempting to add dockerfile for SFU 2023-11-20 14:00:47 +11:00
mcottontensor 0f5e2d9e12
Merge branch 'master' into new_docs 2023-11-17 12:53:10 +11:00
Matthew Cotton 1aad4a6e58
Adding info about simulcast streaming from UE 2023-11-17 12:49:52 +11:00
Matthew Cotton 65ebcfe03e
Adding SFU docs. 2023-11-17 11:16:08 +11:00
Matthew Cotton d5efa38acf
Fixing readme filename. Maybe? 2023-11-17 10:28:18 +11:00
mcottontensor 7d3a6b64c4
Delete SignallingWebServer/Readme.md
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-11-17 10:25:48 +11:00
Matthew Cotton 32d6e2b0b1
Updating main doc to link to signalling docs 2023-11-17 10:23:35 +11:00
Matthew Cotton 7f54018320
Adding some new documentation for signalling 2023-11-17 10:22:29 +11:00
William Belcher 5bea564755
Update platform scripts so that the BASH_LOCATION works on Mac as well (#428) 2023-11-16 16:19:40 +10:00
mcottontensor 7320398ef3
Merge pull request #427 from mcottontensor/legacynamefix
Fixing missed function rename.
2023-11-15 11:42:00 +11:00
Matthew Cotton 082284b6e6
Fixing missed function rename. 2023-11-15 11:39:56 +11:00
mcottontensor a7afbc3d0c
Merge pull request #425 from mcottontensor/initial_bitrates
Bitrate negotiation on stream startup
2023-11-14 08:49:22 +11:00
Matthew Cotton ffcf7b4b3a
Typo 2023-11-10 16:23:01 +11:00
Matthew Cotton bbf812b9c2
More shuffling. 2023-11-10 16:09:00 +11:00
Matthew Cotton 8847ee7440
Small cleanup. 2023-11-10 15:49:57 +11:00
Matthew Cotton 72ebc33558
- Adding support for requesting min/max bitrates on initial negotiation
(offer/answer) from the player
- Fixing bitrate values units and parsing.
2023-11-09 11:01:30 +11:00
mcottontensor edef2915ee
Merge pull request #420 from mcottontensor/ps_expose
Exposing the pixelstreaming interface to the browser.
2023-11-02 12:43:10 +11:00
mcottontensor 1e051866c1
Merge pull request #419 from mcottontensor/fix_reconnect_flow
Rewriting a bunch of reconnect and disconnect handling.
2023-11-02 12:26:18 +11:00
mcottontensor 458e8aa9cb
Merge branch 'master' into fix_reconnect_flow 2023-11-02 09:27:34 +11:00
mcottontensor ea7b1ea030
Merge branch 'master' into ps_expose 2023-11-02 09:27:13 +11:00
Matthew Cotton 9cb4d2c572
Exposing the pixelstreaming interface to the browser. This allows automation to directly interact with the pixelstreaming features for testing etc. 2023-11-02 09:22:24 +11:00
mcottontensor 5cffec3b42
Merge pull request #414 from mcottontensor/remove_login
Removing authentication features.
2023-11-02 09:13:35 +11:00
Matthew Cotton ac6450fbbd
A few small tweaks to text and some comments. 2023-11-01 16:58:17 +11:00
Matthew Cotton 449079871c
Little name cleanup for ease of use 2023-11-01 16:48:08 +11:00
mcottontensor 3af6bff71b
Merge branch 'master' into remove_login 2023-11-01 13:29:06 +11:00
Matthew Cotton 84c8b96a66
Rewriting a bunch of reconnect and disconnect handling. Fixes #401 2023-11-01 13:16:56 +11:00
mcottontensor 44d6c4c829
Update RELEASE_VERSION
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-11-01 10:35:34 +11:00
Matthew Cotton 912265a6f8
Updating frontend library versions 2023-11-01 10:34:12 +11:00
Matthew Cotton 537acc2476
Updating package lock 2023-11-01 10:33:27 +11:00
Matthew Cotton c78be0404c
Updating frontend library versions 2023-11-01 10:32:46 +11:00
mcottontensor 41e7029e0c
Update package.json
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-11-01 10:30:44 +11:00
mcottontensor e90a685096
Merge pull request #417 from EpicGames/master
Merging master into 5.4
2023-11-01 10:29:09 +11:00
mcottontensor da0a871234
Merge branch 'UE5.4' into master 2023-11-01 10:26:11 +11:00
mcottontensor b16bb6c75c
Merge pull request #416 from EpicGames/backport/UE5.4/pr-411
[UE5.4] Merge pull request #411 from mcottontensor/streamer_ids_fix
2023-11-01 10:05:25 +11:00
Matthew Cotton 22de24e61a Fixing sfu forwarding.
(cherry picked from commit 7f07f4b29e)
2023-10-31 22:57:16 +00:00
Matthew Cotton fdb6d6d8a0 Catching case where we're sanitizing a streamer id that is numeric. Updating docs to remove PreferSFU (SFU is just selected as a streamer now).
(cherry picked from commit 339aa088cc)
2023-10-31 22:57:16 +00:00
Matthew Cotton 3d7bfb0723 Updating the handling of generating new legacy streamer and sfu ids.
(cherry picked from commit 403fe39f4b)
2023-10-31 22:57:16 +00:00
Matthew Cotton 63f8a320e1 Cleanup and fixing sfu behaviour.
(cherry picked from commit adfca6c42d)
2023-10-31 22:57:16 +00:00
Matthew Cotton 78d68a8a8b Removing PreferSFU option since this is now handled with the stream selection option. Fixing browser behaviour when multiple streamers detected (previous failed tests).
(cherry picked from commit bbcfe8a6b5)
2023-10-31 22:57:16 +00:00
Matthew Cotton e88871c226 Just some small cleanup
(cherry picked from commit 2ce53023ee)
2023-10-31 22:57:16 +00:00
Matthew Cotton 48b256753a Fixing the windows build script nuking the PATH env variable.
(cherry picked from commit 090cc89b08)
2023-10-31 22:57:16 +00:00
Matthew Cotton ddb4c4776e Allowing SFU to work with multiple streamers.
(cherry picked from commit c0e715ca9d)
2023-10-31 22:57:16 +00:00
Matthew Cotton f8de08ab61 working on handling multiple sfus gracefully
(cherry picked from commit 01d8056bee)
2023-10-31 22:57:16 +00:00
Matthew Cotton 1e5d075d5f Better handling of streamer ids. Specifically legacy ids.
(cherry picked from commit 127feac2e4)
2023-10-31 22:57:16 +00:00
mcottontensor 25024f6848
Merge pull request #411 from mcottontensor/streamer_ids_fix
Fixes for Streamers changing IDs when connecting & Updates to SFU behaviour relating to multi streamer changes.
2023-11-01 09:56:47 +11:00
Matthew Cotton 7f07f4b29e
Fixing sfu forwarding. 2023-10-31 12:54:41 +11:00
Luke Bermingham f2cde5176a
Update link to frontend docs to be /frontend
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-10-31 09:07:28 +10:00
Luke Bermingham 5c65721da3
Update link to frontend docs to go to /frontend
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-10-31 09:05:57 +10:00
Matthew Cotton ea74d91658
Removing authentication features. 2023-10-31 09:44:41 +11:00
mcottontensor b97dcb11cd
Merge pull request #410 from timbotimbo/faketouch-ui
[Frontend] Fix faketouch capturing touches on UI.
2023-10-31 09:28:19 +11:00
Matthew Cotton 339aa088cc
Catching case where we're sanitizing a streamer id that is numeric. Updating docs to remove PreferSFU (SFU is just selected as a streamer now). 2023-10-31 09:17:26 +11:00
Matthew Cotton 403fe39f4b
Updating the handling of generating new legacy streamer and sfu ids. 2023-10-30 16:52:26 +11:00
Matthew Cotton adfca6c42d
Cleanup and fixing sfu behaviour. 2023-10-27 15:15:41 +11:00
Matthew Cotton bbcfe8a6b5
Removing PreferSFU option since this is now handled with the stream selection option. Fixing browser behaviour when multiple streamers detected (previous failed tests). 2023-10-26 10:01:31 +11:00
Matthew Cotton 2ce53023ee
Just some small cleanup 2023-10-25 11:59:45 +11:00
Matthew Cotton 090cc89b08
Fixing the windows build script nuking the PATH env variable. 2023-10-25 11:50:33 +11:00
Matthew Cotton c0e715ca9d
Allowing SFU to work with multiple streamers. 2023-10-25 11:49:55 +11:00
Matthew Cotton 01d8056bee
working on handling multiple sfus gracefully 2023-10-24 15:01:34 +11:00
Matthew Cotton 127feac2e4
Better handling of streamer ids. Specifically legacy ids. 2023-10-24 11:42:30 +11:00
timbotimbo 1478eceb9b
Fix faketouch capturing touches on UI.
Signed-off-by: timbotimbo <timbotimbo@users.noreply.github.com>
2023-10-23 13:46:50 +02:00
mcottontensor d8007a3530
Merge pull request #409 from EpicGames/backport/UE5.4/pr-381
[UE5.4] Merge pull request #381 from New-Game-Plus/fix-video-autoplay
2023-10-23 15:35:18 +11:00
Bramford Horton 23eb2601e1 Fix/allow video autoplay without click
(cherry picked from commit 75cd975400)
2023-10-23 03:32:47 +00:00
mcottontensor 52f8a17e48
Merge pull request #381 from New-Game-Plus/fix-video-autoplay
Fix/allow video autoplay without click
2023-10-23 14:32:22 +11:00
mcottontensor ac6cafae85
Merge pull request #406 from EpicGames/backport/UE5.4/pr-403
[UE5.4] Merge pull request #403 from mcottontensor/mm_linux_fix
2023-10-23 14:13:21 +11:00
Matthew Cotton df2ef8ba04 Small fix to allow the matchmaker start scripts to find the custom install of node.
(cherry picked from commit c76284041e)
2023-10-23 03:04:14 +00:00
mcottontensor 8a6a5bbfc5
Merge pull request #403 from mcottontensor/mm_linux_fix
Allowing the Matchmaker to run on linux.
2023-10-23 14:03:47 +11:00
Matthew Cotton c76284041e
Small fix to allow the matchmaker start scripts to find the custom install of node. 2023-10-23 11:29:27 +11:00
William Belcher 2a21ee6566
Ensure that we have a non-null codecId when we try to update the preferred codec (#400) 2023-10-19 16:17:37 +10:00
William Belcher bf6dcade68
Update SignallingWebServer bash platform scripts to default to Linux (#399) 2023-10-19 14:55:10 +10:00
William Belcher ee82bd398c
Update SignallingWebServer platform scripts to support Mac (#389)
* Update SignallingWebServer platform scripts to support Mac x86_64 and Arm64

* Update bash scripts to default to Linux

* Update coturn URLs to use the binaries provided by the PixelStreamingInfrastructure
2023-10-19 14:40:52 +10:00
github-actions[bot] 952b309c71
Expose JSS InsertionPoint (#397)
Signed-off-by: timbotimbo <timbotimbo@users.noreply.github.com>
Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
(cherry picked from commit 8ba410154d)

Co-authored-by: timbotimbo <timbotimbo@users.noreply.github.com>
2023-10-19 10:48:19 +10:00
timbotimbo 8ba410154d
Expose JSS InsertionPoint (#390)
Signed-off-by: timbotimbo <timbotimbo@users.noreply.github.com>
Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-10-19 10:46:40 +10:00
github-actions[bot] 16d80e27e1
Handle statsPanel or settingsPanel being undefined. (#394)
Signed-off-by: timbotimbo <timbotimbo@users.noreply.github.com>
(cherry picked from commit af5339bec8)

Co-authored-by: timbotimbo <timbotimbo@users.noreply.github.com>
2023-10-19 10:44:18 +10:00
timbotimbo af5339bec8
Handle statsPanel or settingsPanel being undefined. (#392)
Signed-off-by: timbotimbo <timbotimbo@users.noreply.github.com>
2023-10-19 10:42:58 +10:00
github-actions[bot] 1301fde89a
Bump @babel/traverse from 7.21.3 to 7.23.2 in /Frontend/library (#388)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.3 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
(cherry picked from commit 81c3f52f84)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-10-17 14:26:44 +10:00
github-actions[bot] ad85b02b5e
Bump postcss from 8.4.21 to 8.4.31 in /Frontend/implementations/react (#386)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
(cherry picked from commit 55b771e633)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-10-17 14:24:19 +10:00
dependabot[bot] 81c3f52f84
Bump @babel/traverse from 7.21.3 to 7.23.2 in /Frontend/library (#384)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.3 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 14:23:03 +10:00
github-actions[bot] 828123c6c8
Bump postcss in /Frontend/implementations/typescript (#383)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
(cherry picked from commit 1a749cea8a)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 14:10:42 +10:00
dependabot[bot] 55b771e633
Bump postcss from 8.4.21 to 8.4.31 in /Frontend/implementations/react (#379)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 14:09:37 +10:00
dependabot[bot] 1a749cea8a
Bump postcss in /Frontend/implementations/typescript (#380)
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.21 to 8.4.31.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.21...8.4.31)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-17 14:07:22 +10:00
Bramford Horton 75cd975400 Fix/allow video autoplay without click 2023-10-10 14:42:38 +13:00
Belchy06 4c3eda6e9b Update coturn install to pull different binaries depending on os arch 2023-10-03 14:45:29 +10:00
Belchy06 20a409526d Update supported architectures 2023-10-03 13:35:37 +10:00
Belchy06 354a97e11d Add Mac support to bash platform scripts 2023-10-03 13:31:16 +10:00
mcottontensor bb388fec27
Merge pull request #374 from rt-nikowiss/master
Fix emitUIInteraction method
2023-09-21 15:47:37 +10:00
rt-nikowiss aa12e8967a
Fix emitUIInteraction method
Signed-off-by: rt-nikowiss <94848689+rt-nikowiss@users.noreply.github.com>
2023-09-18 14:37:42 +02:00
mcottontensor 2902bdc22b
Merge pull request #373 from MWillWallT/master
Fix broken SFU Layer images
2023-09-14 11:29:38 +10:00
Michael Wallace 830e1440b4
Merge branch 'master' into master 2023-09-14 11:26:36 +10:00
MWillWallT e858b809b7 Merge branch 'master' of https://github.com/MWillWallT/PixelStreamingInfrastructure 2023-09-14 11:24:01 +10:00
MWillWallT fef026bfad Fix broken images 2023-09-14 11:23:56 +10:00
github-actions[bot] 3816f4b535
Remove unit conversion for bitrate from URL. URL is already in kbps (#369) (#372)
(cherry picked from commit eb9a665a0a)

Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-09-14 10:47:07 +10:00
William Belcher eb9a665a0a
Remove unit conversion for bitrate from URL. URL is already in kbps (#369) 2023-09-14 10:45:22 +10:00
mcottontensor 55ab66076e
Update RELEASE_VERSION
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-09-13 13:00:50 +10:00
mcottontensor f6d724a3fe
Update RELEASE_VERSION
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-09-13 12:56:26 +10:00
mcottontensor cabf32a879
Touching ui-library to trigger build action.
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-09-13 12:55:25 +10:00
mcottontensor 24b8082f35
Merge pull request #368 from EpicGames/master
Updating workflows.
2023-09-13 12:53:41 +10:00
mcottontensor f7bde64c42
Merge pull request #367 from mcottontensor/update_workflows
Updating workflows so some can be manually started.
2023-09-13 12:52:17 +10:00
Matthew Cotton 66fc4bc68e
Updating workflows so some can be manually started. 2023-09-13 12:51:39 +10:00
mcottontensor f7c4fd2f3c
Merge pull request #366 from EpicGames/master
Merging in master for some missed 5.3 references.
2023-09-13 12:49:01 +10:00
mcottontensor e2ccbfb28b
Merge pull request #365 from mcottontensor/5.4-fixes
Missed some 5.3 references and updating package-lock
2023-09-13 12:45:24 +10:00
Matthew Cotton bc2e34821b
Missed some 5.3 references and updating package-lock 2023-09-13 12:44:41 +10:00
mcottontensor e6797144d4
Merge pull request #364 from EpicGames/master
Merging in master to generate npm packages.
2023-09-13 12:21:09 +10:00
mcottontensor 6b8f4f6324
Merge pull request #361 from MWillWallT/master
Shift of Matchmaker and SFU pages to Infra
2023-09-13 12:17:46 +10:00
mcottontensor d6b55fccec
Merge branch 'master' into master 2023-09-13 12:17:09 +10:00
mcottontensor 292b75e9f3
Merge pull request #363 from mcottontensor/update_actions
Updated actions to reference 5.4
2023-09-13 12:16:50 +10:00
Matthew Cotton 0cde16d497
Updated actions to reference 5.4 2023-09-13 12:15:34 +10:00
mcottontensor 759cb9ae3f
Merge pull request #362 from mcottontensor/54npmlibs
Updating NPM libs to 5.4
2023-09-13 12:11:44 +10:00
Matthew Cotton 57c895482d
merged in upstream master 2023-09-13 12:07:12 +10:00
Matthew Cotton a0f874bcc0
Updated 5.3 references to 5.4. 2023-09-13 12:02:51 +10:00
MWillWallT e1d0904417 Shift of Matchmaker and SFU pages to Infra 2023-09-13 11:38:34 +10:00
mcottontensor eb5313b734
Merge branch 'UE5.3' into master
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-09-12 14:46:51 +10:00
mcottontensor 9fd9c0618c
Update README.md
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-09-12 14:38:58 +10:00
I-m-None-user 8b25630852
Update Application.ts add check ipad
Signed-off-by: I-m-None-user <122039782+I-m-None-user@users.noreply.github.com>
2023-09-12 14:15:10 +10:00
mcottontensor c21f4c5b97
Merge pull request #342 from I-m-None-user/patch-1
Update Application.ts add check ipad
2023-09-12 13:51:16 +10:00
mcottontensor 3dcc00db4d
Merge branch 'master' into patch-1 2023-09-12 13:50:37 +10:00
Matthew Cotton 2a34f838e2
merged in origin master 2023-09-12 13:49:42 +10:00
mcottontensor 19d1ac8d90
Merge pull request #315 from gingernaz/data_channel_latency_test
Add: DataChannel latency test
2023-09-12 11:18:08 +10:00
mcottontensor 4722f86aab
Merge branch 'master' into data_channel_latency_test
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-09-12 11:16:43 +10:00
mcottontensor 54eb5b73b5
Merge pull request #192 from kass-kass/master
Added ability to mute/unmute mic and enable it later than PixelStreaming creation time
2023-09-12 11:15:21 +10:00
mcottontensor 9e802dde92
Merge branch 'master' into master 2023-09-12 10:58:54 +10:00
Luke Bermingham ca70087ce0
Update CHANGELOG.md
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-12 08:41:07 +10:00
Luke Bermingham 10d61a2ea1
Bump RELEASE_VERSION to 0.0.4
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-12 08:30:28 +10:00
Luke Bermingham 7f8a86435e Change /ui-library version back to 0.0.4 and update package-lock.json 2023-09-12 08:27:19 +10:00
Luke Bermingham 53bf8deece
Bump /ui-library to 0.0.5 in UE5.3
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-11 22:37:56 +10:00
Luke Bermingham c0b941acf7
Update publish-ui-library-to-npm.yml
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-11 22:36:31 +10:00
Luke Bermingham 301b624806
Lock github action to NodeJS 18.17.0
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-11 22:36:08 +10:00
Luke Bermingham 2159bb4272
Update publish-ui-library-to-npm.yml
Lock github action to NodeJS 18.17.0

Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-11 22:35:06 +10:00
Luke Bermingham dbb54d14eb
Lock NodeJS version to 18.17.0 in Github action
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-11 22:33:42 +10:00
Luke Bermingham 75cb711348
Bump /ui-library version to 0.0.4 in 5.3
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-11 22:29:59 +10:00
Luke Bermingham a907d2b926
Bump frontend /library version to 0.0.3 in 5.3
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-11 22:28:53 +10:00
Luke Bermingham 1468459f93
Update CHANGELOG.md
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-11 22:23:38 +10:00
mcottontensor aeddc8fe74
Merge pull request #358 from mcottontensor/early_reconnect
Auto connect on new streamer
2023-09-11 13:11:44 +10:00
Matthew Cotton 3dc7591f7f
Renamed config parameter 2023-09-11 13:09:16 +10:00
Matthew Cotton 8d0d498310
Exposing retry delay as a configurable value 2023-09-11 12:59:07 +10:00
mcottontensor 0dcef11533
Merge branch 'master' into early_reconnect 2023-09-11 10:42:34 +10:00
Matthew Cotton d8ba9e5e95
Adding better support for auto connect to first available streamer. 2023-09-11 10:31:13 +10:00
Matthew Cotton 24577cdfd8
Revert "Adding the ability to connect automatically when a new streamer joins the signalling server"
This reverts commit bcc6bc7d08.
2023-09-08 13:52:07 +10:00
Matthew Cotton bcc6bc7d08
Adding the ability to connect automatically when a new streamer joins the signalling server 2023-09-08 12:58:41 +10:00
Luke Bermingham 65238c6c47
Added headings to building various pieces of the frontend
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-08 11:53:41 +10:00
Luke Bermingham efcbf7be9f
Added prerequisites step for setting up node dev environment
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-08 11:51:30 +10:00
Matthew Cotton e6093213fc
Merge remote-tracking branch 'upstream/master' 2023-09-08 11:41:14 +10:00
Luke Bermingham fc1c98cd66
Update README.md for building frontend library and implementations
Also added warning about Node version we support for the frontend.

Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-09-08 11:39:28 +10:00
William Belcher ff10231cb0
Update CONTRIBUTING.md (#357)
Signed-off-by: William Belcher <william.belcher@xa.epicgames.com>
2023-09-07 13:58:26 +10:00
github-actions[bot] 81e065f84e
Consume the context menu event instead of sending a mouse up (#356)
(cherry picked from commit e4946abba2)

Co-authored-by: Michael Stopa <michael.stopa@xa.epicgames.com>
2023-09-04 15:07:12 +10:00
Michael Stopa 6e14dbb34b
Merge pull request #354 from StomyPX/no_context_event
Consume the context menu event instead of sending a mouse up
2023-09-04 14:28:20 +09:30
Michael Stopa e4946abba2
Consume the context menu event instead of sending a mouse up 2023-09-04 12:45:01 +09:30
Matthew Cotton 081b66b25d
dont want these anymore 2023-09-04 12:18:16 +10:00
Matthew Cotton b158ebdada
Does this one work?? 2023-09-04 11:40:33 +10:00
Matthew Cotton b6f63fc30e
I dont understand why schedule isnt working 2023-09-04 11:37:38 +10:00
Matthew Cotton d1afc80bba
bad formatting maybe? 2023-09-04 11:32:30 +10:00
Matthew Cotton 7368039b50
Maybe this will work 2023-09-04 11:29:03 +10:00
Matthew Cotton 33b675f09b
Adding scheduled test to master 2023-09-04 11:17:47 +10:00
Matthew Cotton 1c7ecb67aa
Merge branch 'data_channel_latency_test' of github.com:gingernaz/PixelStreamingInfrastructure into naz-data-channel-latency-test 2023-08-31 16:06:03 +10:00
Matthew Cotton ae7861a69f
Removing empty file left over from merge conflict. 2023-08-31 16:05:43 +10:00
mcottontensor d319891abb
Merge branch 'master' into data_channel_latency_test 2023-08-31 16:02:43 +10:00
Matthew Cotton b3c5c82d25
Merged in master and resolved conflicts. 2023-08-31 15:59:13 +10:00
mcottontensor 6b742d3577
Merge pull request #211 from kasp1/master
New matchmaker queue screen with easy customization
2023-08-31 15:34:41 +10:00
Matthew Cotton 22489b6e1e
Merge remote-tracking branch 'origin/master' into kasp1-matchmaker-queue 2023-08-31 14:33:19 +10:00
Matthew Cotton 158ca0a909
Fixing missing import 2023-08-31 13:58:51 +10:00
Matthew Cotton 4806a96bf2
Resolving conflicts with main 2023-08-31 13:54:02 +10:00
Matthew Cotton ce3bf3e080
Merge remote-tracking branch 'upstream/master' 2023-08-31 12:48:28 +10:00
Luke Bermingham 19438d8654
Update README.md
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-08-25 08:55:36 +10:00
Luke Bermingham b0294701d5
Update README.md
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-08-25 08:53:33 +10:00
Denis Phoenix 21abd3892d
Add contribution guideline `CONTRIBUTING.md` (#352)
* Added CONTRIBUTING.md
2023-08-24 16:39:21 +10:00
mcottontensor 2b73af42f7
Merge branch 'EpicGames:master' into master 2023-08-01 10:48:26 +10:00
I-m-None-user 2ff5a8aa0f
Update Application.ts add check ipad
Signed-off-by: I-m-None-user <122039782+I-m-None-user@users.noreply.github.com>
2023-07-31 14:42:44 +02:00
William Belcher 20dd81f606
Update LatencyTest handler to accept data (#340) 2023-07-27 17:09:06 +10:00
William Belcher 5b250b7148
Refactor SignallingWebServer to a single docker file (#337) for UE5.3 (#339) 2023-07-27 16:41:26 +10:00
William Belcher 6051ea52fc
Refactor SignallingWebServer to a single docker file (#337) for UE5.2 (#338) 2023-07-27 16:41:15 +10:00
William Belcher 1373f41257
Add: Protocol structures can now contain strings (#336)
* Modify message struct: no need for byteLength; strings are now supported as a type

* Remove TwoWayMap as it's no longer needed. Add getter for streamer handlers

* Fix keyup sending too many args

* Update log message to print js types. Update default protocol

* Register handler for TextboxEntry. Add check to make sure handler is defined when registering for a fromStreamer message

* Remove unncessary import from player.ts

* Update data type to reflect that it could contain strings
2023-07-27 16:03:11 +10:00
William Belcher 4078871c1c
Remove bash specific dockerfile in favour of using the platform agnostic version in /SignallingWebServer (#337) 2023-07-27 15:47:44 +10:00
github-actions[bot] 5fcd53614e
Properly inject new params into SDP to get stereo back on Chrome (#332) 2023-07-27 10:08:43 +10:00
github-actions[bot] 333c5fbab8
Properly inject new params into SDP to get stereo back on Chrome (#332) 2023-07-27 10:07:09 +10:00
Michael Stopa 11387d642a
Have matchmaker ask for authentication instead of EACCES (#333) 2023-07-27 10:04:17 +10:00
Michael Stopa 1947760e2b
Properly inject new params into SDP to get stereo back on Chrome (#332) 2023-07-27 10:02:26 +10:00
Michael Stopa 7ca3114698
Bump Node.js to latest LTS (#321) 2023-07-27 09:52:01 +10:00
mcottontensor f20b38bb30
Delete run-test.yml
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-07-25 11:45:43 +10:00
mcottontensor c6bfe08766
Create run-test.yml
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-07-25 11:35:47 +10:00
dependabot[bot] 5f7ebe1471
Bump word-wrap from 1.2.3 to 1.2.5 in /Frontend/ui-library (#327)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.5)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 13:32:52 +10:00
dependabot[bot] 42dedc698b
Bump word-wrap from 1.2.3 to 1.2.4 in /Frontend/library (#320)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 13:31:10 +10:00
dependabot[bot] bc845a0d99
Bump tough-cookie from 4.1.2 to 4.1.3 in /Frontend/library (#290)
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v4.1.2...v4.1.3)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-24 13:30:48 +10:00
Joel-Patteson af6fc4bd18
Fix TypeError: console.warning is not a function (#326)
Co-authored-by: Joel Patteson <joelpatteson7@gmail.com>
2023-07-22 21:51:44 +10:00
Joel-Patteson b3f966bc8e
Fix TypeError: console.warning is not a function (#325)
Co-authored-by: Joel Patteson <joelpatteson7@gmail.com>
2023-07-22 21:43:18 +10:00
github-actions[bot] aa13c254fd
Ensure touch is relative to absolute location of parent rect (#316)
(cherry picked from commit 4e370e5439)

Co-authored-by: Michael Stopa <michael.stopa@xa.epicgames.com>
2023-07-19 15:24:16 +10:00
github-actions[bot] 8914e616db
Ensure touch is relative to absolute location of parent rect (#316)
(cherry picked from commit 4e370e5439)

Co-authored-by: Michael Stopa <michael.stopa@xa.epicgames.com>
2023-07-19 15:24:01 +10:00
Michael Stopa 4e370e5439
Ensure touch is relative to absolute location of parent rect (#316) 2023-07-19 15:23:18 +10:00
Nazar Rudenko 2a952a11b3
Friendly output instead of NaN if UE doesn't support the test 2023-07-19 10:39:06 +10:00
Nazar Rudenko 930c396aeb
Add: DataChannelLatencyTest 2023-07-19 10:13:17 +10:00
Mirek Kaspar c6c3e4cd87 fixed html template of the matchmaker queue screen 2023-07-18 10:11:13 +03:00
Mirek Kaspar 80d165950f Merge https://github.com/EpicGames/PixelStreamingInfrastructure 2023-07-18 10:08:09 +03:00
github-actions[bot] 2dea212048
Rename implementations/EpicGames to implementations/typescript (#177)
Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
(cherry picked from commit d92e46e301)

Co-authored-by: gunsha <gonzaller@gmail.com>
2023-07-18 16:33:06 +10:00
gunsha d92e46e301
Rename implementations/EpicGames to implementations/typescript (#177)
Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-07-18 16:32:02 +10:00
github-actions[bot] 149e526360
Fix: Firefox console errors `TypeError: this.preferredCodec.split is not a function` (#310) (#313)
* Fix select codec on Firefox

* Restore indentation in cirrus

(cherry picked from commit 503b565bc5)

Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-07-18 15:32:43 +10:00
github-actions[bot] 3bcffb2b68
Fix: Firefox console errors `TypeError: this.preferredCodec.split is not a function` (#310) (#312)
* Fix select codec on Firefox

* Restore indentation in cirrus

(cherry picked from commit 503b565bc5)

Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-07-18 15:32:30 +10:00
William Belcher 503b565bc5
Fix: Firefox console errors `TypeError: this.preferredCodec.split is not a function` (#310)
* Fix select codec on Firefox

* Restore indentation in cirrus
2023-07-18 14:48:42 +10:00
github-actions[bot] 6852ed39c0
Show player count in stats panel (#303)
(cherry picked from commit 6bdbf509eb)

Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-07-18 14:32:51 +10:00
github-actions[bot] 9598623648
Show player count in stats panel (#303)
(cherry picked from commit 6bdbf509eb)

Co-authored-by: William Belcher <william.belcher@xa.epicgames.com>
2023-07-18 14:32:28 +10:00
William Belcher d5d8db1bd9
Add .backportrc.json to enable auto merging of backport PRs
Signed-off-by: William Belcher <william.belcher@xa.epicgames.com>
2023-07-18 14:31:26 +10:00
William Belcher 2ad3674a56
Update stale message on Issues and PRs
Signed-off-by: William Belcher <william.belcher@xa.epicgames.com>
2023-07-18 14:06:54 +10:00
William Belcher 6bdbf509eb
Show player count in stats panel (#303) 2023-07-18 12:04:18 +10:00
mcottontensor 4124bb759f
Update stale.yml
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-07-18 11:27:50 +10:00
mcottontensor 8646e47653
Merge pull request #306 from Belchy06/stale
Update stale workflow to also auto close stale issues and PRs
2023-07-18 11:23:16 +10:00
William Belcher 10d6cd699c
update stale workflow to also auto close stale issues and PRs 2023-07-18 11:17:59 +10:00
William Belcher fc4872f464
Update stale.yml to include manual trigger (#305)
Signed-off-by: William Belcher <william.belcher@xa.epicgames.com>
2023-07-18 10:53:32 +10:00
William Belcher 1e11c6a78d
Add github action for PR and Issue management (#304)
* Add stale action for issues/PRs that are inactive for 30 days
2023-07-18 10:37:59 +10:00
mcottontensor d6ee7301e7
Merge pull request #288 from EpicGames/mcottontensor-patch-1
Update SignallingProtocol.md
2023-07-07 14:10:58 +10:00
mcottontensor f56cb1089f
Update SignallingProtocol.md
Signed-off-by: mcottontensor <80377552+mcottontensor@users.noreply.github.com>
2023-07-07 10:47:11 +10:00
mcottontensor f5993e7f5c
Merge pull request #271 from mcottontensor/ss_message_doc
Signaling message reference doc
2023-06-29 15:35:06 +10:00
mcottontensor 5be8187e71
Merge branch 'master' into ss_message_doc 2023-06-29 15:34:54 +10:00
mcottontensor 36631072a3
Merge pull request #285 from mcottontensor/fix_autoconnect
Fixing auto reconnect to not reconnect when the page is refreshed.
2023-06-29 15:34:19 +10:00
Matthew Cotton 530cf9e804 Merge branch 'fix_autoconnect' of github.com:mcottontensor/PixelStreamingInfrastructure into fix_autoconnect 2023-06-29 15:20:25 +10:00
Matthew Cotton 6c20889572 A little bit more context for the change
Signed-off-by: Matthew Cotton <matt@tensorworks.com.au>
2023-06-29 15:20:02 +10:00
Matthew Cotton c24549366d A little bit more context for the change 2023-06-29 15:05:50 +10:00
Matthew Cotton c4d6ddb204 Removing debug prints 2023-06-29 14:48:57 +10:00
Matthew Cotton 3a480aa540 Only reconnecting when the close event is not a locally initiated close event 2023-06-29 14:32:40 +10:00
Matthew Cotton 1f3644eff7 Bump release version 2023-06-21 11:31:53 +10:00
Matthew Cotton 039354b151 Bump ui-library version 2023-06-21 11:31:23 +10:00
Matthew Cotton 8d6ebc6910 Bump library version 2023-06-21 11:29:39 +10:00
mcottontensor 0fd71b6887 Merge pull request #273 from jibranabsarulislam/jibranabsarulislam/add-CRD-events
Include create, reconnect, and update events (with associated tests)
2023-06-21 11:28:52 +10:00
Matthew Cotton 4b5660cd0c Bump release version 2023-06-21 11:28:06 +10:00
Matthew Cotton 31d363455c Bump ui-library version 2023-06-21 11:26:51 +10:00
Matthew Cotton a6eb05993b Bump library version 2023-06-21 11:23:46 +10:00
mcottontensor bfd1e66973 Merge pull request #273 from jibranabsarulislam/jibranabsarulislam/add-CRD-events
Include create, reconnect, and update events (with associated tests)
2023-06-21 11:08:08 +10:00
Matthew Cotton daf9a3fd35 Adding version to the signalling doc 2023-06-21 10:01:52 +10:00
Matthew Cotton 551c399555 Renamed the protocol document 2023-06-20 14:38:36 +10:00
Matthew Cotton 6d33562d4c Adding root readme 2023-06-20 14:37:21 +10:00
mcottontensor 69f8c7befb
Merge branch 'master' into ss_message_doc 2023-06-20 14:32:48 +10:00
Matthew Cotton a4362d52c2 Adding small note about legacy streamer ids 2023-06-20 14:31:25 +10:00
Matthew Cotton c28b466385 Adding an example sequence diagram for a typical streaming session 2023-06-20 12:09:58 +10:00
Matthew Cotton 71598b4503 Adding some extra comments on some messages. 2023-06-19 13:02:25 +10:00
Matthew Cotton 338c22e26b Updating signalling messages doc 2023-06-19 12:07:53 +10:00
Matthew Cotton fc3af1dcdd updating references with some extra formatting 2023-06-19 10:34:46 +10:00
Matthew Cotton 001170f0e2 Adding some signaling message documentation 2023-06-16 12:19:09 +10:00
Matthew Cotton 900f45ce4d Revert "Adding some documentation for signaling messages"
This reverts commit 322fc69206.
2023-06-16 12:17:23 +10:00
Matthew Cotton daa4024dac Revert "Separating out parts of cirrus into modules to clean up the code a little"
This reverts commit c89193e46d.
2023-06-16 12:13:46 +10:00
Matthew Cotton 322fc69206 Adding some documentation for signaling messages 2023-06-16 12:01:23 +10:00
Matthew Cotton c89193e46d Separating out parts of cirrus into modules to clean up the code a little 2023-06-14 13:43:32 +10:00
Luke Bermingham 357a665994
Update RELEASE_VERSION to 0.0.2 to include iOS fullscreen fix
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-06-14 13:02:59 +10:00
Luke Bermingham d9dfa12b62 Bumped ui library to 0.0.2 to include iOS fullscreen fix 2023-06-14 13:01:18 +10:00
Luke Bermingham e7cd55a303
Update RELEASE_VERSION to 0.6.4 to include iOS fullscreen fix
Signed-off-by: Luke Bermingham <1215582+lukehb@users.noreply.github.com>
2023-06-14 12:46:05 +10:00
Luke Bermingham 63227f6b1c Upgraded ui library version to 0.4.3 as part of iOS fix 2023-06-14 12:44:24 +10:00
Luke Bermingham 124fc6658d
Merge pull request #268 from Belchy06/UE5.3
Bring BR 266 to 5.3
2023-06-12 16:53:51 +10:00
Luke Bermingham 21f5685ae7
Merge pull request #267 from Belchy06/UE5.2
Bring PR 266 to 5.2
2023-06-12 16:50:49 +10:00
William Belcher 5811f32d1c
Re-enable iOS and iPadOS fullscreen. Additionaly, only use the native…
… webplayer in iOS
2023-06-12 16:44:05 +10:00
Mirek Kaspar f76abf3e33 New matchmaker queue screen with easy customization 2023-04-29 15:41:54 +02:00
k_a_s_s 252b958bb7 Added documentation for UseMic 2023-04-07 13:41:46 +02:00
k_a_s_s 89d8505181 Added option to (un)mute mic in PixelStreaming 2023-04-07 13:25:02 +02:00
166 changed files with 15295 additions and 5453 deletions

4
.backportrc.json Normal file
View File

@ -0,0 +1,4 @@
{
"autoMerge": true,
"autoMergeMethod": "squash"
}

View File

@ -7,10 +7,10 @@ assignees: ''
---
**UE Version: **
**UE Version:**
E.g. UE 5.1.1
**Frontend Version: **
**Frontend Version:**
E.g. UE5.3-0.3.0
**Problem component**

View File

@ -2,7 +2,8 @@ name: Publish Cirrus container
on:
push:
branches: ['UE5.3']
workflow_dispatch:
branches: ['UE5.4']
paths: ['SignallingWebServer/**']
jobs:
@ -27,6 +28,15 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
tags: 'ghcr.io/epicgames/pixel-streaming-signalling-server:5.3'
tags: 'ghcr.io/epicgames/pixel-streaming-signalling-server:5.4'
push: true
file: SignallingWebServer/Dockerfile
-
name: Build and push the SFU container image
uses: docker/build-push-action@v3
with:
context: .
tags: 'ghcr.io/epicgames/pixel-streaming-sfu:5.4'
push: true
file: SFU/Dockerfile

View File

@ -2,7 +2,7 @@ name: Releases
on:
push:
branches: ['UE5.3']
branches: ['UE5.4']
paths: ['RELEASE_VERSION']
jobs:
@ -48,16 +48,16 @@ jobs:
working-directory: ./Frontend/ui-library
run: npm run build-all
- name: Install implementations/EpicGames deps
working-directory: ./Frontend/implementations/EpicGames
- name: Install implementations/typescript deps
working-directory: ./Frontend/implementations/typescript
run: npm ci
- name: NPM link frontend and ui-library into implementations/EpicGames
working-directory: ./Frontend/implementations/EpicGames
- name: NPM link frontend and ui-library into implementations/typescript
working-directory: ./Frontend/implementations/typescript
run: npm link ../../library ../../ui-library
- name: Build implementations/EpicGames
working-directory: ./Frontend/implementations/EpicGames
- name: Build implementations/typescript
working-directory: ./Frontend/implementations/typescript
run: npm run build-all
- name: Move all content into output directory for archiving
@ -70,7 +70,7 @@ jobs:
path: 'PixelStreamingInfrastructure-${{ github.ref_name }}-${{ steps.getversion.outputs.version }}'
type: 'tar'
filename: '${{ github.ref_name }}-${{ steps.getversion.outputs.version }}.tar.gz'
exclusions: '.git .github output Frontend/Docs Frontend/library/dist Frontend/library/types Frontend/library/node_modules Frontend/ui-library/dist Frontend/ui-library/types Frontend/ui-library/node_modules Frontend/implementations/EpicGames/node_modules'
exclusions: '.git .github output Frontend/Docs Frontend/library/dist Frontend/library/types Frontend/library/node_modules Frontend/ui-library/dist Frontend/ui-library/types Frontend/ui-library/node_modules Frontend/implementations/typescript/node_modules'
- name: Archive Release .zip
uses: thedoctor0/zip-release@0.7.1

View File

@ -1,7 +1,8 @@
name: Publish frontend lib
on:
push:
branches: ['UE5.3']
workflow_dispatch:
branches: ['UE5.4']
paths: ['Frontend/library/package.json']
jobs:
build:
@ -13,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16.x'
node-version: '18.17.0'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build

View File

@ -1,7 +1,8 @@
name: Publish frontend-ui lib
on:
push:
branches: ['UE5.3']
workflow_dispatch:
branches: ['UE5.4']
paths: ['Frontend/ui-library/package.json']
jobs:
build:
@ -13,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16.x'
node-version: '18.17.0'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build

View File

@ -0,0 +1,42 @@
name: Run signalling tests
on:
workflow_dispatch:
push:
branches: ['signalling_tester']
paths: ['SignallingWebServer/**']
pull_request:
branches: ['signalling_tester']
paths: ['SS_Test/**']
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./
permissions:
contents: write
steps:
- name: "Checkout source code"
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'
- name: Run signalling server
working-directory: ./SignallingWebServer
run: ./platform_scripts/bash/run_local.sh &
- name: Install library deps
working-directory: ./SS_Test
run: npm ci
- name: Run frontend lib tests
working-directory: ./SS_Test
run: npm run start

28
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Mark and close stale issues and pull requests
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Issues go stale after 30 days of inactivity. Please comment or re-open the issue if you are still interested in getting this issue fixed.'
stale-issue-label: 'stale'
exempt-issue-labels: 'enhancement'
stale-pr-message: 'PRs go stale after 30 days of inactivity. Please comment or re-open the PR if you are still working on this PR.'
stale-pr-label: 'stale'
exempt-pr-labels: 'awaiting-approval'
days-before-stale: 30
days-before-close: 0

View File

@ -4,10 +4,46 @@ The changelog is a summary of commits between releases of Unreal Engine.
As a reminder each UE-X branch/tag in this repository corresponds to a version of Unreal Engine.
## [UE 5.3 (Coming soon)](https://github.com/EpicGames/PixelStreamingInfrastructure/commits/UE5.3)
Coming soon...
## [UE 5.3 (Current)](https://github.com/EpicGames/PixelStreamingInfrastructure/commits/UE5.3)
## [UE 5.2 (Current)](https://github.com/EpicGames/PixelStreamingInfrastructure/commits/UE5.2)
### Features
- Protocol structures can now contain strings by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/336
- Added the ability for the frontend peer to auto connect when a new streamer is available by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/358
### Enhancements
- Upgrade 5.2 to 5.3 in libraries, docs, log messages, build pipelines by @lukehb in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/262
- Include create, reconnect, and update events (with associated tests) by @jibranabsarulislam in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/273
- Add github action for PR and Issue management by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/304
- Update stale.yml by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/305
- Update stale workflow to also auto close stale issues and PRs by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/306
- Show player count in stats panel by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/303
- Change implementations/EpicGames to implementations/typescript #166 by @gunsha in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/177
- Refactor SignallingWebServer to a single docker file by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/337
- Update LatencyTest handler to accept input data by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/340
- Add contribution guideline `CONTRIBUTING.md` by @DenisTensorWorks in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/352
- New matchmaker queue screen with easy customization by @kasp1 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/211
- Updated CONTRIBUTING.md with backport rules by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/357
### Documentation
- Signaling message reference doc by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/271
- Update SignallingProtocol.md by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/288
### Bug fixes
- Fixed auto reconnect to not reconnect when the page is refreshed. by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/285
- Fixed iOS touch when settings panel is open by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/274
- Fixed Firefox console errors `TypeError: this.preferredCodec.split is not a function` by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/310
- Fixed ensuring touch is relative to absolute location of parent rect by @StomyPX in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/316
- Fixed injecting new params into SDP to get stereo back on Chrome by @StomyPX in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/332
- Fixed matchmaker asking for OS authentication instead of erroring out with EACCESS by @StomyPX in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/333
- Fixed consuming the context menu event instead of sending a mouse up by @StomyPX in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/354
### Security
- Bump tough-cookie from 4.1.2 to 4.1.3 in /Frontend/library by @dependabot in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/290
- Bump word-wrap from 1.2.3 to 1.2.4 in /Frontend/library by @dependabot in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/320
- Bump word-wrap from 1.2.3 to 1.2.5 in /Frontend/ui-library by @dependabot in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/327
- Bump Node.js to latest LTS by @StomyPX in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/321
## [UE 5.2](https://github.com/EpicGames/PixelStreamingInfrastructure/commits/UE5.2)
### Features
- Added minimal sample React implementation by @hmuurine in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/159
@ -18,6 +54,12 @@ Coming soon...
- Added experimental support for WebXR based experiences by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/85
### Docs
- New general docs page/ToC + new security page. by @MWillWallT in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/254
- Update README to mention container images require being part of Epic's Github org by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/248
- Update platform_scripts readme.md to explain the different scripts by @lukehb in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/224
- Improve signalling Server readme @DenisTensorWorks in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/223
- Adding microphone feature documentation for UE5.2 by @DenisTensorWorks in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/228
- Adding microphone feature documentation by @DenisTensorWorks in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/208
- Added new general docs page/ToC + new security page. by @MWillWallT in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/254
- Settings Panel Documentation by @MWillWallT in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/135
- Customised Pixel Streaming Player Page by @MWillWallT in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/90
@ -29,6 +71,14 @@ Coming soon...
- Update Docs to remove broken links by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/122
### Enhancements
- Add repository health status in the form of Github badges table on readme.md by @lukehb in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/265
- Re-enable iOS and iPadOS fullscreen by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/266
- Changed forwarded logs to Cyan, added warning for missing playerId by @StomyPX in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/253
- Added "media-playout" to prevent spam in Aggregated Stats by @chasse20 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/232
- Added 'stat PixelStreamingGraphs' to showcase frontend #229 by @devrajgadhvi in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/238
- Bump socket.io-parser from 4.2.2 to 4.2.4 in /Matchmaker by @dependabot in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/244
- Bump engine.io from 6.4.0 to 6.4.2 in /Matchmaker by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/220
- Allow inheritance of webrtcPlayerController and webXrController by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/209
- Pass command line args when calling run_local.bat by @lukehb in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/109
- Customize frontend styles through UI API by @hmuurine in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/133
- Force URL param settings when receiving initial application settings by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/134
@ -57,6 +107,10 @@ Coming soon...
- Replaced hardcoded log path with given parameter path by @Mirmidion in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/39
### Bug fixes
- Fixed viewport resizing not always working due to improperly calling timer. by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/247
- Fixed hovering mouse mode set in URL being overridden on refresh. by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/246
- Fixed matchmaker directing users to http when the signalling server is using https by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/245
- Fixed reconnects will be attempted even when a disconnect is triggered by afk timeout by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/215
- Fixed datachannels not working when using the SFU by @mcottontensor in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/137
- Fixed SFU having clashing datachannel/stream ids, now using mediasoup's internal stream ids for SCTP by @StomyPX in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/129
- Fixed controller indices from multiple peers would clash by @Belchy06 in https://github.com/EpicGames/PixelStreamingInfrastructure/pull/165

68
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,68 @@
# Welcome to Pixel Streaming contribution guide
First and foremost, thank you for your time and contribution to Pixel Streaming!
We are proud and excited to be a part of a passionate community that continuously helps us improve Pixel Streaming 🎉
If you are not familiar with contributing on GitHub, have a look at the [official documentation](https://docs.github.com/get-started) to learn more about repositories structure, forks, branches, commits, issues, and PRs.
### Code of conduct
Please remain patient, courteous, and professional at all times. Any form of spam, abuse, or discrimination will not be tolerated.
## Getting started
### Creating issues
If you have encountered a bug, have suggestions for our documentation or infrastructure, or would like to propose a feature that could enhance Pixel Streaming in various use case scenarios, you can raise this with us by creating a new issue.
1. First, search all open and closed issues [here](https://github.com/EpicGames/PixelStreamingInfrastructure/issues?q=is%3Aissue+) - your issue may have already been discussed or addressed.
2. If your issue doesn't exist, open a new issue [here](https://github.com/EpicGames/PixelStreamingInfrastructure/issues/new/choose) by selecting a bug or feature request.
3. Make sure to fill in the template as much as possible; any information you can provide, such as repro steps, crash stacks, screenshots, etc., can help us triage and fix the problem as quickly as possible.
4. Keep an eye on the status of your issue; our developers or other users might reach out with requests for more information. If this happens, issues that have not received a response in over 30 days will be automatically closed.
5. Be patient while waiting on a resolution; we prioritize the issues internally and some less critical features (however much we'd love to implement them!) will take a backseat to more pressing priorities, so some issues can take a while to get resolved.
### Creating pull requests (PR)
If you have a solution to a problem you've encountered or to any other open issue, you can create a pull request with your changes.
1. Fork the repo and branch off of the `main` branch in your fork.
2. Implement your changes in your branch and make sure your commits are Verified! Signed commits are required for merging! [Github Signing Documentation](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
3. Do as much testing as you can, and when you are happy, tidy up your work and commit the update.
4. Create a [pull request](https://github.com/EpicGames/PixelStreamingInfrastructure/pulls) and don't forget to link it to an issue if there's an existing one. Add as much information as possible to your PR: describe the problem your change solves, mention any testing you have done and attach any relevant documents and screenshots.
5. If your are contributing a PR for a new feature, we strongly encourage you to accompany it with relevant documentation and a detailed description of the tests you have done. PRs that don't have this information may take a long time to be addressed, since our team will have to do the testing.
6. If your PR is good to go, we will merge it in. Woohoo! Thank you for your time and efforts! 🎉
7. Keep a close eye on your PR - quite often, our developers will review your PR and leave comments; we might request some minor code changes and modifications, style unification, or leave any general comments and questions that are preventing us from merging the PR.
8. If we do not hear from you after requesting more information within 30 days, the PR will auto-close. In this case, we might elect to open our own PR and re-use some of the changes that you proposed, supplemented with anything else that was required to be added in your original PR.
9. If your PR fixes a problem in the previous [still-supported UE branches](https://github.com/EpicGames/PixelStreamingInfrastructure#versions), feel free to add the `auto-backport` and `auto-backport-to-UEX.X` labels. You'll need to add a `auto-backport-to-UEX.X` label for each branch you wish your change to be merged back to. Note that if a change to any of the previous branches is not trivial and requires a lot of testing and compatibility checks, we might elect to close it if we do not think that it brings enough value to the branch.
### Other ways to contribute
- Keep an eye on our repo and stay active on existing issues and PRs; you can help by adding informative comments to the discussions, additional repro steps, repros in different environments, or any suggestions as to what could be causing the issue and how it could be solved.
- Work on [issues labeled for community](https://github.com/EpicGames/PixelStreamingInfrastructure/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). We specifically created this label to mark issues that we would love the community to help us with.
- Create documentation for undocumented features. Please open an issue first, so our developers can provide you with some guidance.
- Write more unit test coverage.
- Document functions in the public API that are not documented.
- Write new frontend implementations using another web framework, e.g. Angular, Vue, etc.
- Perform QA on different engine versions, particularly previews, and create issues based on the bugs that you have found.
## Coding style
- TypeScript should be used over JavaScript.
- All TypeScript should adhere to the following [linting rules](https://github.com/EpicGames/PixelStreamingInfrastructure/blob/master/Frontend/library/.eslintrc.js).
- Names should follow US English spelling.
- All public functions/API should have comments.
- Code formatting should adhere to the following [whitespace and indentation rules](https://github.com/EpicGames/PixelStreamingInfrastructure/blob/master/Frontend/library/.prettierrc.json).
- All new features should have accompanying unit tests and documentation when they are submitted.
- Prefer early returns in `if` statements to decrease indentation.
- Prefer functions to not exceed ~20 lines.
- Prefer comments in longer functions.
- Prefer verbosity over syntactic sugar.
- Prefer exporting a minimal public API surface for iteration and support reasons.
- Try not to exceed three levels of nesting in a function.
## Documentation style
All documentation should be written in US English and follow correct grammar and spelling. Endeavour to lay out the document in a logical fashion with headings, lists, and bullet points where appropriate.
Documentation should be broken up into separate `.md` files per directory, ideally with a `readme.md` file in the root of each top-level directory for a component to explain it. Where appropriate, these documentation pages should be linked to a table of contents in the relevant part of the repository.
## Legal
© 2004-2024, Epic Games, Inc. Unreal and its logo are Epics trademarks or registered trademarks in the US and elsewhere.

View File

@ -9,4 +9,4 @@ Welcome to the general documentation page for Pixel Streaming. This page serves
## Legal
© 2004-2023, Epic Games, Inc. Unreal and its logo are Epics trademarks or registered trademarks in the US and elsewhere.
© 2004-2024, Epic Games, Inc. Unreal and its logo are Epics trademarks or registered trademarks in the US and elsewhere.

View File

@ -9,6 +9,7 @@ The following options are available in the frontend library to customize input:
| HoveringMouseMode | false | Determines whether or not the video element captures and locks the mouse when the player interacts with the widget. When enabled, the mouse cursor hovers over the player widget without interacting with it. In order to send the mouse movements to the input controller of the Unreal Engine application, the user needs to click and hold the left button of the mouse. Otherwise, clicking on the player widget causes it to capture and lock the mouse cursor. Any further movements of the mouse are passed immediately to the input controller in the Unreal Engine application. This typically allows the user to move and rotate the camera by simply dragging the mouse. To release the cursor from the control of the player widget, the user can press the **Esc** key. |
| SuppressBrowserKeys | true | When this setting is enabled, the player widget will intercept function keys (**F1** to **F12**) and the **Tab** key, and pass those keypress events through to the Unreal Engine application rather than allowing the browser to process them normally.| This means, for example, that while this setting is active, pressing **F5** will not refresh the player page in the browser. Instead, that event is passed through to the Unreal Engine application, and has its usual function of switching the view to visualize shader complexity.
| FakeMouseWithTouches | false | When this option is enabled and the user is viewing the stream on a device with a touch screen such as a smartphone or tablet, this setting causes single-finger touch events to be interpreted by the Unreal Engine application as mouse clicks and drag events. Enabling this setting can provide users on mobile devices with the ability to partially control your Unreal Engine application, even when the application's input controller does not specifically handle touch input events. |
| UseMic | false | Indicates whether or not the stream should be created with a microphone track which is sent to the UE application. This microphone track can be listened to using the [`UPixelStreamingAudioComponent`](https://docs.unrealengine.com/5.0/en-US/API/Plugins/PixelStreaming/UPixelStreamingAudioComponent/). If this flag is enabled, a microphone audio track is created (if the browser settings allow for it) and is actively sending audio. For saving bandwidth or other functionality, it's possible to mute the microphone track by calling `PixelStreaming`'s `muteMicrophone`, and later re-enable it with `unmuteMicrophone`. If the `PixelStreaming` object was created without this flag, it's possible to enable it later by calling `unmuteMicrophone(true)`, which will use the `forceEnable` parameter to add the track and trigger a full reconnection (this is a heavier operation that takes a while). |
When creating a frontend implementation, these options are visible via the [`Config`](/Frontend/library/src/Config/Config.ts) object required in order to create a [`PixelStreaming`](/Frontend/library/src/PixelStreaming/PixelStreaming.ts) stream for your frontend application. Simply set the values you want before initializing the stream object.

View File

@ -1,5 +1,5 @@
## Recommended Reading
We recommend starting with the [sample implementations](/Frontend/implementations/EpicGames/src) in order to judge how to put a new player page together and integrate it with your Unreal Engine application. Additionally, if you have cloned the Pixel Streaming Infrastructure repository and made upstream changes, you can fork the repo and make a pull request.
We recommend starting with the [sample implementations](/Frontend/implementations/typescript/src) in order to judge how to put a new player page together and integrate it with your Unreal Engine application. Additionally, if you have cloned the Pixel Streaming Infrastructure repository and made upstream changes, you can fork the repo and make a pull request.
## Using the default Player Webpage
The Pixel Streaming Signalling and Web Server provides a sample player page that is already set up to stream in media from your Unreal Engine application and to send mouse, keyboard, and touch events back to the application. You can use this default player page as-is, if it meets your needs.

View File

@ -1,6 +1,6 @@
## HTML Page Requirements
Most of the HTML that will end up on the final page will actually be introduced by the Pixel Streaming application itself. Several example HTML pages are provided in the [sample implementations](/Frontend/implementations/EpicGames/src) where you can see the base page is very minimal, only serving as a space for the application to attach to and fill. The only concrete requirements are for ensuring there's sufficient space taken up by the element being attached to for the viewport to be visible on screen. In the sample implementations this is simply a `<body>` tag set to fill the screen without scrolling.
Most of the HTML that will end up on the final page will actually be introduced by the Pixel Streaming application itself. Several example HTML pages are provided in the [sample implementations](/Frontend/implementations/typescript/src) where you can see the base page is very minimal, only serving as a space for the application to attach to and fill. The only concrete requirements are for ensuring there's sufficient space taken up by the element being attached to for the viewport to be visible on screen. In the sample implementations this is simply a `<body>` tag set to fill the screen without scrolling.
```html
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
@ -20,14 +20,14 @@ Most of the HTML that will end up on the final page will actually be introduced
</html>
```
As can be seen in the [sample implementations](/Frontend/implementations/EpicGames/src/player.ts), you must specify which element on the page the Pixel Streaming viewport is to be appended to. In the sample implementations this is typically done in the `document.body.onload` event listener and in this case appended to the `document.body` element in the DOM, causing it to fill the whole page.
As can be seen in the [sample implementations](/Frontend/implementations/typescript/src/player.ts), you must specify which element on the page the Pixel Streaming viewport is to be appended to. In the sample implementations this is typically done in the `document.body.onload` event listener and in this case appended to the `document.body` element in the DOM, causing it to fill the whole page.
[//]: # (This has yet to be done)
### Player File Location and URL
You have a few options for where you can place your custom HTML player page, and how client browsers can access it.
* You can create a new implementation page and place it in [`/Frontend/implementations/EpicGames/src/`](/Frontend/implementations/EpicGames/src) alongside the sample implementations. This must consist of both a base `.html` page and the `.ts` source for your application's entrypoint. This will then be accessible by appending the name of the `html` file to IP address or hostname of the computer running the Signalling Server.
* You can create a new implementation page and place it in [`/Frontend/implementations/typescript/src/`](/Frontend/implementations/typescript/src) alongside the sample implementations. This must consist of both a base `.html` page and the `.ts` source for your application's entrypoint. This will then be accessible by appending the name of the `html` file to IP address or hostname of the computer running the Signalling Server.
For example, the sample `stresstest` page can be accessed on a locally-running infrastructure at `http:/127.0.0.1/stresstest.html`.
* You can customize the `HomepageFile` parameter for the Signaling and Web Server, and set the path to the filename of your custom HTML player page relative to the [Frontend implementations source folder](/Frontend/implementations/src). It will then be accessible when you access the IP address or hostname of the computer running the Signaling and Web Server.
* You can also use the **AdditionalRoutes** parameter for the Signaling and Web Server to customize the mapping between URL paths and local folders on your computer.

View File

@ -19,7 +19,6 @@ This page will be updated with new features and commands as they become availabl
| **Browser send offer** | The browser will start the WebRTC handshake instead of the Unreal Engine application. This is an advanced setting for users customising the frontend. Primarily for backwards compatibility for 4.x versions of the engine. |
| **Use microphone** | Will start receiving audio input from your microphone and transmit it to the Unreal Engine. |
| **Start video muted** | Muted audio when the stream starts. |
| **Prefer SFU** | Will attempt to use the Selective Forwarding Unit (SFU), if you have one running. |
| **Is quality controller?** | Makes the encoder of the Pixel Streaming Plugin use the current browser connection to determine the bandwidth available, and therefore the quality of the stream encoding. **See notes below** |
| **Force mono audio** | Force the browser to request mono audio in the SDP. |
| **Force TURN** | Will attempt to connect exclusively via the TURN server. Will not work without an active TURN server. |

View File

@ -5,7 +5,7 @@ The **frontend** refers to the HTML, CSS, images, and JavaScript/TypeScript code
The frontend consists of two packages:
1. [lib-pixelstreamingfrontend](/Frontend/library/): the core Pixel Streaming frontend for WebRTC, settings, input, and general functionality.
2. [lib-pixelstreamingfrontend-ui](/Frontend/implementations/EpicGames): the reference UI that users can either optionally apply on top of the core library or build on top of.
2. [lib-pixelstreamingfrontend-ui](/Frontend/implementations/typescript): the reference UI that users can either optionally apply on top of the core library or build on top of.
## Docs
@ -29,7 +29,7 @@ The TypeScript libraries are provided as both an [NPM](https://www.npmjs.com/set
## Usage from source
When developing your own Pixel Streaming experience the intent is you will start with this library and extend it through the use of
its public API. We have provided an example of this workflow in our [implementations/EpicGames](/Frontend/implementations/EpicGames), which is an implementation of this library.
its public API. We have provided an example of this workflow in our [implementations/typescript](/Frontend/implementations/typescript), which is an implementation of this library.
## Contributing
@ -37,6 +37,14 @@ If part of the library is not exposed and you wish to extend it, please do so in
## Developing
⚠️ Only NodeJS LTS 18.17.0 is officially supported, some newer versions on NodeJS **WILL BREAK YOUR BUILD** ⚠️
### Prerequisites
- Install NodeJS LTS 18.17.0 on your system.
- Install npm globally using: `npm install npm -g` (yes this is required)
### Building the Library
Changes to the library occur in the [/library](/Frontend/library) directory and require you to have NodeJS installed as part of your development environment.
Once you have NodeJS installed:
@ -44,16 +52,28 @@ Once you have NodeJS installed:
- `npm install`
- `npm run build`
The default user interface is provided in [/ui-library](/Frontend/ui-library) directory. You can either use it or provide your own user interface. To build the default UI, run:
### Building the UI-Library
The user interface library is provided in [/ui-library](/Frontend/ui-library) directory. You can either use it or provide your own user interface. To build run:
- Follow the steps to build the library first
- `cd ui-library`
- `npm install`
- `npm run build`
- `npm run build-all`
This will produce `player.js` under the `SignallingWebServer/Public` directory - this is the default UI.
### Building the default UI
The default user interface is provided under [/implementations/typescript](/Frontend/implementations/typescript). To build run:
- Follow the steps to build the libary and ui-library first
- `cd implementations/typescript`
- `npm install`
- `npm run build-all`
This will produce `player.html` and `player.js` under the `SignallingWebServer/Public` directory - this is the default UI.
### Making your own UI
We recommend studying [/ui-library](/Frontend/ui-library) and [player.ts](/Frontend/implementations/EpicGames/src/player.ts)/[player.html](/Frontend/implementations/EpicGames/src/player.html), or alternatively the sample React implementation in [implementations/react](/Frontend/implementations/react), then once you have copied and modified the [package.json](/Frontend/implementations/EpicGames/package.json) and `.ts` into your own `implementation/your_implementation` directory, the process is similar:
We recommend studying [/ui-library](/Frontend/ui-library) and [player.ts](/Frontend/implementations/typescript/src/player.ts)/[player.html](/Frontend/implementations/typescript/src/player.html), or alternatively the sample React implementation in [implementations/react](/Frontend/implementations/react), then once you have copied and modified the [package.json](/Frontend/implementations/typescript/package.json) and `.ts` into your own `implementation/your_implementation` directory, the process is similar:
- `cd implementation/your_implementation`
- `npm build-all`
@ -67,4 +87,4 @@ The [/library](/Frontend/library) project has unit tests that test the Pixel Str
## Legal
Copyright &copy; 2023, Epic Games. Licensed under the MIT License, see the file [LICENSE](./LICENSE) for details.
Copyright &copy; 2024, Epic Games. Licensed under the MIT License, see the file [LICENSE](./LICENSE) for details.

View File

@ -1,3 +0,0 @@
# Configuration of the Frontend UI
Todo

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1 +0,0 @@
The images directory

View File

@ -1,37 +0,0 @@
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<!DOCTYPE html>
<html style="width: 100%; height: 100%">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
<!-- Required - the Login style sheet -->
<link rel="stylesheet" type="text/css" href="./assets/css/login.css">
<!-- Optional: set some favicons -->
<link id="favPng" rel="icon" type="image/png" href="./assets/images/favicon.png">
<!-- Optional: set a title for your page -->
<title>Pixel Streaming Login</title>
</head>
<body style="width: 100vw; height: 100vh; min-height: -webkit-fill-available; font-family: 'Montserrat'; margin: 0px">
<form action="/login" method="post">
<div class="entry">
<input type="text" id="username" name="username" placeholder="Username"
autocomplete="username">
</div>
<div class="entry">
<input type="password" id="password" name="password" placeholder="Password"
autocomplete="current-password">
</div>
<div class="entry button">
<button type="submit">LOGIN</button>
</div>
</form>
</body>
</html>

View File

@ -1,2 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.

View File

@ -0,0 +1,9 @@
# Angular Implementations
Here are a selection of community contributed implementations of Angular frontends.
- [cheikhnadiouf](https://github.com/cheikhnadiouf)'s implementation - [LINK](https://github.com/cheikhnadiouf/PixelStreamingInfrastructure/tree/AngularImplementations/Frontend/implementations/angular)
If you wish to contribute your own example frontend, please open an issue/PR.
**Disclaimer: We do not warrant these for any fitness of purpose, nor do we maintain them.**

View File

@ -1576,9 +1576,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"dev": true,
"funding": [
{
@ -2500,10 +2500,16 @@
}
},
"node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@ -2831,9 +2837,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.21",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"funding": [
{
@ -2843,10 +2849,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},

View File

@ -1,5 +1,5 @@
{
"name": "@epicgames-ps/react-pixelstreamingfrontend-react-ue5.3",
"name": "@epicgames-ps/react-pixelstreamingfrontend-react-ue5.4",
"version": "0.0.1",
"description": "",
"main": "./src/index.tsx",

View File

@ -17,7 +17,8 @@ export const App = () => {
AutoConnect: true,
ss: 'ws://localhost:80',
StartVideoMuted: true,
HoveringMouse: true
HoveringMouse: true,
WaitForStreamer: true
}}
/>
</div>

View File

@ -5,7 +5,7 @@ import {
Config,
AllSettings,
PixelStreaming
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
export interface PixelStreamingWrapperProps {
initialSettings?: Partial<AllSettings>;

View File

@ -1,11 +1,11 @@
{
"name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.3",
"name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.4",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.3",
"name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.4",
"version": "0.0.1",
"devDependencies": {
"css-loader": "^6.7.3",
@ -1390,8 +1390,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"dev": true,
"funding": [
{
@ -2207,9 +2208,16 @@
}
},
"node_modules/nanoid": {
"version": "3.3.4",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@ -2505,8 +2513,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.21",
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"funding": [
{
@ -2516,10 +2525,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@ -4886,8 +4899,9 @@
}
},
"follow-redirects": {
"version": "1.15.2",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"dev": true
},
"forwarded": {
@ -5463,8 +5477,9 @@
}
},
"nanoid": {
"version": "3.3.4",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true
},
"negotiator": {
@ -5680,11 +5695,12 @@
}
},
"postcss": {
"version": "8.4.21",
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}

View File

@ -1,5 +1,5 @@
{
"name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.3",
"name": "@epicgames-ps/reference-pixelstreamingfrontend-ue5.4",
"version": "0.0.1",
"description": "",
"main": "./src/player.ts",
@ -9,8 +9,8 @@
"watch": "npx webpack --watch",
"serve": "webpack serve --config webpack.dev.js",
"serve-prod": "webpack serve --config webpack.prod.js",
"build-all": "npm link ../../library ../../ui-library && cd ../../library && npm run build && cd ../ui-library && npm run build-all && cd ../implementations/EpicGames && npm run build",
"build-dev-all": "npm link ../../library ../../ui-library && cd ../../library && npm run build-dev && cd ../ui-library && npm run build-dev-all && cd ../implementations/EpicGames && npm run build-dev"
"build-all": "cd ../../library && npm run build && cd ../ui-library && npm run build-all && cd ../implementations/typescript && npm link ../../library ../../ui-library && npm run build",
"build-dev-all": "cd ../../library && npm run build-dev && cd ../ui-library && npm run build-dev-all && cd ../implementations/typescript && npm link ../../library ../../ui-library && npm run build-dev"
},
"devDependencies": {
"webpack-cli": "^5.0.1",

View File

@ -4,7 +4,7 @@ A plugin library that can be optionally applied on top of the core library to cr
**This is great starting point for building your UI or studying the Pixel Streaming feature set.**
![Frontend](/Frontend/implementations/EpicGames/docs/images/frontend.jpg)
![Frontend](/Frontend/implementations/typescript/docs/images/frontend.jpg)
### Key features
- An info panel (screen right) that provides a UI for displaying live statistics to the user.
@ -14,4 +14,4 @@ A plugin library that can be optionally applied on top of the core library to cr
### Adding it to your project
`npm i @epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.3`
`npm i @epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.4`

View File

@ -1,203 +1,203 @@
:root {
--democolor0: rgba(15, 15, 15, 1);
--democolor1: #000000;
--democolor2: #FFFFFF;
--democolor3: #0585fe;
--democolor4: rgba(26, 26, 26, 1);
--democolor5: rgba(36, 36, 36, 1);
--democolor6: rgba(53, 53, 53, 1);
--democolor7: rgba(180, 180, 180, 1);
}
body {
margin: 0px;
padding: 0px;
height: 100vh;
width: 100vw;
background-color: var(--democolor5);
font-family: verdana,sans-serif;
color: var(--democolor7);
}
code {
background-color: var(--democolor6);
}
.wrapper {
display: flex;
align-items: stretch;
height: 100%;
width: 100%;
}
.spaced-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
row-gap: 1vh;
}
#infocontainer {
padding: 0.5em;
font-size: large;
min-height: 15vh;
max-height: 15vh;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
#infoinstructions {
background-color: var(--democolor4);
padding: 0.5em;
font-size: medium;
flex-grow: 1;
overflow-y: auto;
}
#content {
width: 100%;
flex-direction: column;
display: flex;
align-items: initial;
justify-content: center;
}
#exampletitle {
padding: 1em;
display: flex;
flex-direction: column;
justify-content: center;
align-items: baseline;
}
#sidebar {
min-width: 250px;
max-width: 250px;
background-color: var(--democolor4);
transition: all 0.3s;
font-size: small;
border-color: var(--democolor4);
border-style: solid;
border-width: 0.33em 0.33em 0em 0em;
align-content: flex-start;
display: flex;
flex-direction: column;
}
#sidebar-tab-header {
width: 100%;
}
#sidebar-header {
padding: 0.25rem 2rem 0.25rem 0.5rem;
background-color: var(--democolor5);
border-top: 1px solid var(--democolor3);
border-radius: 5px 5px 0px 0px;
margin-left: 1em;
width: -moz-fit-content;
width: fit-content;
}
#sidebarContent {
max-height:100%;
overflow-y:auto;
background-color: var(--democolor5);
flex-grow: 1;
padding-left: 1em;
padding-right: 1em;
}
#sidebar-example-selector {
background-color: var(--democolor5);
padding-top: 1em;
padding-bottom: 1em;
padding-left: 1em;
}
#psdemotext {
font-size: large;
}
#playercontainer {
background-color: var(--democolor0);
flex-grow: 1;
font-family: 'Montserrat', sans-serif;
}
select {
font-size: large;
padding: 0.5em;
color: var(--democolor7);
background-color: var(--democolor0);
border: 2px solid var(--democolor6);
outline: none !important;
border-radius: 5px;
}
select:hover {
color: var(--democolor2);
}
a, a:hover, a:focus {
color: inherit;
text-decoration: none;
transition: all 0.3s;
}
#sidebar ul.components {
padding: 20px 0;
}
#sidebar ul p {
color: #fff;
padding: 10px;
}
#sidebar ul li a {
padding: 10px;
font-size: 1.1em;
display: block;
}
#sidebar ul li a:hover {
color: #7386D5;
background: #fff;
}
#sidebar ul li.active > a, a[aria-expanded="true"] {
color: #fff;
background: #212f44;
/*#f90;*/
}
ul ul a {
font-size: 0.9em !important;
padding-left: 30px !important;
background: #354b6d;
}
a[data-toggle="collapse"] {
position: relative;
}
.dropdown-toggle::after {
display: block;
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
}
.characterBtn {
width: 100%;
cursor: pointer;
}
.characterBtn:hover {
box-shadow: var(--democolor3) 0px 0px 0px 3px;
}
.characterBtn:active {
box-shadow: var(--democolor5) 0px 0px 0px 3px;
:root {
--democolor0: rgba(15, 15, 15, 1);
--democolor1: #000000;
--democolor2: #FFFFFF;
--democolor3: #0585fe;
--democolor4: rgba(26, 26, 26, 1);
--democolor5: rgba(36, 36, 36, 1);
--democolor6: rgba(53, 53, 53, 1);
--democolor7: rgba(180, 180, 180, 1);
}
body {
margin: 0px;
padding: 0px;
height: 100vh;
width: 100vw;
background-color: var(--democolor5);
font-family: verdana,sans-serif;
color: var(--democolor7);
}
code {
background-color: var(--democolor6);
}
.wrapper {
display: flex;
align-items: stretch;
height: 100%;
width: 100%;
}
.spaced-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
row-gap: 1vh;
}
#infocontainer {
padding: 0.5em;
font-size: large;
min-height: 15vh;
max-height: 15vh;
display: flex;
flex-direction: column;
justify-content: flex-start;
}
#infoinstructions {
background-color: var(--democolor4);
padding: 0.5em;
font-size: medium;
flex-grow: 1;
overflow-y: auto;
}
#content {
width: 100%;
flex-direction: column;
display: flex;
align-items: initial;
justify-content: center;
}
#exampletitle {
padding: 1em;
display: flex;
flex-direction: column;
justify-content: center;
align-items: baseline;
}
#sidebar {
min-width: 250px;
max-width: 250px;
background-color: var(--democolor4);
transition: all 0.3s;
font-size: small;
border-color: var(--democolor4);
border-style: solid;
border-width: 0.33em 0.33em 0em 0em;
align-content: flex-start;
display: flex;
flex-direction: column;
}
#sidebar-tab-header {
width: 100%;
}
#sidebar-header {
padding: 0.25rem 2rem 0.25rem 0.5rem;
background-color: var(--democolor5);
border-top: 1px solid var(--democolor3);
border-radius: 5px 5px 0px 0px;
margin-left: 1em;
width: -moz-fit-content;
width: fit-content;
}
#sidebarContent {
max-height:100%;
overflow-y:auto;
background-color: var(--democolor5);
flex-grow: 1;
padding-left: 1em;
padding-right: 1em;
}
#sidebar-example-selector {
background-color: var(--democolor5);
padding-top: 1em;
padding-bottom: 1em;
padding-left: 1em;
}
#psdemotext {
font-size: large;
}
#playercontainer {
background-color: var(--democolor0);
flex-grow: 1;
font-family: 'Montserrat', sans-serif;
}
select {
font-size: large;
padding: 0.5em;
color: var(--democolor7);
background-color: var(--democolor0);
border: 2px solid var(--democolor6);
outline: none !important;
border-radius: 5px;
}
select:hover {
color: var(--democolor2);
}
a, a:hover, a:focus {
color: inherit;
text-decoration: none;
transition: all 0.3s;
}
#sidebar ul.components {
padding: 20px 0;
}
#sidebar ul p {
color: #fff;
padding: 10px;
}
#sidebar ul li a {
padding: 10px;
font-size: 1.1em;
display: block;
}
#sidebar ul li a:hover {
color: #7386D5;
background: #fff;
}
#sidebar ul li.active > a, a[aria-expanded="true"] {
color: #fff;
background: #212f44;
/*#f90;*/
}
ul ul a {
font-size: 0.9em !important;
padding-left: 30px !important;
background: #354b6d;
}
a[data-toggle="collapse"] {
position: relative;
}
.dropdown-toggle::after {
display: block;
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
}
.characterBtn {
width: 100%;
cursor: pointer;
}
.characterBtn:hover {
box-shadow: var(--democolor3) 0px 0px 0px 3px;
}
.characterBtn:active {
box-shadow: var(--democolor5) 0px 0px 0px 3px;
}

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -1,28 +1,28 @@
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<!DOCTYPE HTML>
<html style="width: 100%; height: 100%">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Optional: apply a font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
<!-- Optional: set some favicons -->
<link rel="shortcut icon" href="./assets/images/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" sizes="96x96" href="./assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">
<!-- Optional: set a title for your page -->
<title>Pixel Streaming</title>
</head>
<!-- The Pixel Streaming player fills 100% of its parent element but body has a 0px height unless filled with content. As such, we explicitly force the body to be 100% of the viewport height -->
<body style="width: 100vw; height: 100vh; min-height: -webkit-fill-available; font-family: 'Montserrat'; margin: 0px">
</body>
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<!DOCTYPE HTML>
<html style="width: 100%; height: 100%">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Optional: apply a font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
<!-- Optional: set some favicons -->
<link rel="shortcut icon" href="./assets/images/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" sizes="96x96" href="./assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">
<!-- Optional: set a title for your page -->
<title>Pixel Streaming</title>
</head>
<!-- The Pixel Streaming player fills 100% of its parent element but body has a 0px height unless filled with content. As such, we explicitly force the body to be 100% of the viewport height -->
<body style="width: 100vw; height: 100vh; min-height: -webkit-fill-available; font-family: 'Montserrat'; margin: 0px">
</body>
</html>

View File

@ -1,24 +1,32 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.3';
const PixelStreamingApplicationStyles =
new PixelStreamingApplicationStyle();
PixelStreamingApplicationStyles.applyStyleSheet();
document.body.onload = function() {
// Example of how to set the logger level
// Logger.SetLoggerVerbosity(10);
// Create a config object
const config = new Config({ useUrlParams: true });
// Create a Native DOM delegate instance that implements the Delegate interface class
const stream = new PixelStreaming(config);
const application = new Application({
stream,
onColorModeChanged: (isLightMode) => PixelStreamingApplicationStyles.setColorMode(isLightMode)
});
// document.getElementById("centrebox").appendChild(application.rootElement);
document.body.appendChild(application.rootElement);
}
// Copyright Epic Games, Inc. All Rights Reserved.
import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.4';
const PixelStreamingApplicationStyles =
new PixelStreamingApplicationStyle();
PixelStreamingApplicationStyles.applyStyleSheet();
// expose the pixel streaming object for hooking into. tests etc.
declare global {
interface Window { pixelStreaming: PixelStreaming; }
}
document.body.onload = function() {
// Example of how to set the logger level
// Logger.SetLoggerVerbosity(10);
// Create a config object
const config = new Config({ useUrlParams: true });
// Create a Native DOM delegate instance that implements the Delegate interface class
const stream = new PixelStreaming(config);
const application = new Application({
stream,
onColorModeChanged: (isLightMode) => PixelStreamingApplicationStyles.setColorMode(isLightMode)
});
// document.getElementById("centrebox").appendChild(application.rootElement);
document.body.appendChild(application.rootElement);
window.pixelStreaming = stream;
}

View File

@ -1,61 +1,61 @@
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<title>Pixel Streaming Showcase</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Optional: set some favicons -->
<link rel="shortcut icon" href="./assets/images/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" sizes="96x96" href="./assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">
<!-- Optional: load a custom font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
<!-- Apply the showcase CSS -->
<link type="text/css" rel="stylesheet" href="./assets/css/showcase.css">
</head>
<body>
<!-- Load images in page body load -->
<img src="./assets/images/Aurora.jpg" style="display: none;" />
<img src="./assets/images/Crunch.jpg" style="display: none;" />
<div class="wrapper">
<!-- The details panels for the examples sidebar -->
<div id="sidebar">
<div id="exampletitle">
<p id="psdemotext"> Select an example:</p>
<select id="exampleSelect" onchange="onExampleChanged(event)" title="Select an example...">
<option>Getting Started</option>
<option>Send Data to UE</option>
<option>Send Commands to UE</option>
</select>
</div>
<div id="sidebar-header">Details</div>
<div id="sidebarContent"></div>
</div>
<div id="content">
<div id="playercontainer"></div>
<div id="infocontainer">
<div id="infoinstructions">
Information here
</div>
</div>
</div>
</div>
</body>
</html>
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<title>Pixel Streaming Showcase</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Optional: set some favicons -->
<link rel="shortcut icon" href="./assets/images/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" sizes="96x96" href="./assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">
<!-- Optional: load a custom font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
<!-- Apply the showcase CSS -->
<link type="text/css" rel="stylesheet" href="./assets/css/showcase.css">
</head>
<body>
<!-- Load images in page body load -->
<img src="./assets/images/Aurora.jpg" style="display: none;" />
<img src="./assets/images/Crunch.jpg" style="display: none;" />
<div class="wrapper">
<!-- The details panels for the examples sidebar -->
<div id="sidebar">
<div id="exampletitle">
<p id="psdemotext"> Select an example:</p>
<select id="exampleSelect" onchange="onExampleChanged(event)" title="Select an example...">
<option>Getting Started</option>
<option>Send Data to UE</option>
<option>Send Commands to UE</option>
</select>
</div>
<div id="sidebar-header">Details</div>
<div id="sidebarContent"></div>
</div>
<div id="content">
<div id="playercontainer"></div>
<div id="infocontainer">
<div id="infoinstructions">
Information here
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,7 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.3';
import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.4';
export const PixelStreamingApplicationStyles = new PixelStreamingApplicationStyle();
PixelStreamingApplicationStyles.applyStyleSheet();

View File

@ -1,7 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { Config, Flags, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.3';
import { Config, Flags, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { Application, PixelStreamingApplicationStyle } from '@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.4';
const PixelStreamingApplicationStyles =
new PixelStreamingApplicationStyle();
PixelStreamingApplicationStyles.applyStyleSheet();

View File

@ -1,48 +1,48 @@
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<!DOCTYPE HTML>
<html style="width: 100%; height: 100%">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Optional: apply a font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
<!-- Optional: set some favicons -->
<link rel="shortcut icon" href="./assets/images/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" sizes="96x96" href="./assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">
<style>
#clickToPlayElement.visible {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
#clickToPlayElement {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
};
</style>
<!-- Optional: set a title for your page -->
<title>Pixel Streaming</title>
</head>
<!-- The Pixel Streaming player fills 100% of its parent element but body has a 0px height unless filled with content. As such, we explicitly force the body to be 100% of the viewport height -->
<body style="width: 100vw; height: 100vh; min-height: -webkit-fill-available; font-family: 'Montserrat'; margin: 0px">
<div id="videoParentElement"></div>
<div id="clickToPlayElement">
<div>Click to play</div>
</div>
</body>
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<!DOCTYPE HTML>
<html style="width: 100%; height: 100%">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Optional: apply a font -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
<!-- Optional: set some favicons -->
<link rel="shortcut icon" href="./assets/images/favicon.ico" type="image/x-icon">
<link rel="icon" type="image/png" sizes="96x96" href="./assets/images/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="32x32" href="./assets/images/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="./assets/images/favicon-16x16.png">
<style>
#clickToPlayElement.visible {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
#clickToPlayElement {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
};
</style>
<!-- Optional: set a title for your page -->
<title>Pixel Streaming</title>
</head>
<!-- The Pixel Streaming player fills 100% of its parent element but body has a 0px height unless filled with content. As such, we explicitly force the body to be 100% of the viewport height -->
<body style="width: 100vw; height: 100vh; min-height: -webkit-fill-available; font-family: 'Montserrat'; margin: 0px">
<div id="videoParentElement"></div>
<div id="clickToPlayElement">
<div>Click to play</div>
</div>
</body>
</html>

View File

@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { Config, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
document.body.onload = function() {
// Example of how to set the logger level
@ -13,6 +13,7 @@ document.body.onload = function() {
AutoConnect: true,
ss: "ws://localhost:80",
StartVideoMuted: true,
WaitForStreamer: true,
}
});

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "@epicgames-ps/lib-pixelstreamingfrontend-ue5.3",
"version": "0.0.1",
"description": "Frontend library for Unreal Engine 5.3 Pixel Streaming",
"name": "@epicgames-ps/lib-pixelstreamingfrontend-ue5.4",
"version": "0.0.3",
"description": "Frontend library for Unreal Engine 5.4 Pixel Streaming",
"main": "dist/lib-pixelstreamingfrontend.js",
"module": "dist/lib-pixelstreamingfrontend.esm.js",
"types": "types/pixelstreamingfrontend.d.ts",

View File

@ -2,7 +2,7 @@
The core library for the browser/client side of Pixel Streaming experiences. **This library contains no UI.**
See [lib-pixelstreamingfrontend-ui](/Frontend/implementations/EpicGames) for an example of how to build UI on top of this library.
See [lib-pixelstreamingfrontend-ui](/Frontend/implementations/typescript) for an example of how to build UI on top of this library.
### Key features
- Create a websocket connection to communicate with the signalling server.
@ -11,5 +11,5 @@ See [lib-pixelstreamingfrontend-ui](/Frontend/implementations/EpicGames) for an
- Opens a datachannel connection sending and receiving custom data (in addition to input).
### Adding it to your project
`npm i @epicgames-ps/lib-pixelstreamingfrontend-ue5.3`
`npm i @epicgames-ps/lib-pixelstreamingfrontend-ue5.4`

View File

@ -23,7 +23,6 @@ export class Flags {
static FakeMouseWithTouches = 'FakeMouseWithTouches' as const;
static IsQualityController = 'ControlsQuality' as const;
static MatchViewportResolution = 'MatchViewportRes' as const;
static PreferSFU = 'preferSFU' as const;
static StartVideoMuted = 'StartVideoMuted' as const;
static SuppressBrowserKeys = 'SuppressBrowserKeys' as const;
static UseMic = 'UseMic' as const;
@ -32,6 +31,7 @@ export class Flags {
static TouchInput = 'TouchInput' as const;
static GamepadInput = 'GamepadInput' as const;
static XRControllerInput = 'XRControllerInput' as const;
static WaitForStreamer = "WaitForStreamer" as const;
}
export type FlagsKeys = Exclude<keyof typeof Flags, 'prototype'>;
@ -54,6 +54,7 @@ export class NumericParameters {
static WebRTCMinBitrate = 'WebRTCMinBitrate' as const;
static WebRTCMaxBitrate = 'WebRTCMaxBitrate' as const;
static MaxReconnectAttempts = 'MaxReconnectAttempts' as const;
static StreamerAutoJoinInterval = 'StreamerAutoJoinInterval' as const;
}
export type NumericParametersKeys = Exclude<
@ -155,10 +156,7 @@ export class Config {
constructor(config: ConfigParams = {}) {
const { initialSettings, useUrlParams } = config;
this._useUrlParams = !!useUrlParams;
this.populateDefaultSettings(this._useUrlParams);
if (initialSettings) {
this.setSettings(initialSettings);
}
this.populateDefaultSettings(this._useUrlParams, initialSettings);
}
/**
@ -172,7 +170,7 @@ export class Config {
/**
* Populate the default settings for a Pixel Streaming application
*/
private populateDefaultSettings(useUrlParams: boolean): void {
private populateDefaultSettings(useUrlParams: boolean, settings: Partial<AllSettings>): void {
/**
* Text Parameters
*/
@ -183,13 +181,15 @@ export class Config {
TextParameters.SignallingServerUrl,
'Signalling url',
'Url of the signalling server',
(location.protocol === 'https:' ? 'wss://' : 'ws://') +
window.location.hostname +
// for readability, we omit the port if it's 80
(window.location.port === '80' ||
window.location.port === ''
? ''
: `:${window.location.port}`),
settings && settings.hasOwnProperty(TextParameters.SignallingServerUrl) ?
settings[TextParameters.SignallingServerUrl] :
(location.protocol === 'https:' ? 'wss://' : 'ws://') +
window.location.hostname +
// for readability, we omit the port if it's 80
(window.location.port === '80' ||
window.location.port === ''
? ''
: `:${window.location.port}`),
useUrlParams
)
);
@ -200,7 +200,9 @@ export class Config {
OptionParameters.StreamerId,
'Streamer ID',
'The ID of the streamer to stream.',
'',
settings && settings.hasOwnProperty(OptionParameters.StreamerId) ?
settings[OptionParameters.StreamerId] :
'',
[],
useUrlParams
)
@ -216,29 +218,31 @@ export class Config {
'Preferred Codec',
'The preferred codec to be used during codec negotiation',
'H264 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
(function (): Array<string> {
const browserSupportedCodecs: Array<string> = [];
// Try get the info needed from the RTCRtpReceiver. This is only available on chrome
if (!RTCRtpReceiver.getCapabilities) {
browserSupportedCodecs.push('Only available on Chrome');
return browserSupportedCodecs;
}
const matcher = /(VP\d|H26\d|AV1).*/;
const codecs =
RTCRtpReceiver.getCapabilities('video').codecs;
codecs.forEach((codec) => {
const str =
codec.mimeType.split('/')[1] +
' ' +
(codec.sdpFmtpLine || '');
const match = matcher.exec(str);
if (match !== null) {
browserSupportedCodecs.push(str);
settings && settings.hasOwnProperty(OptionParameters.PreferredCodec) ?
[settings[OptionParameters.PreferredCodec]] :
(function (): Array<string> {
const browserSupportedCodecs: Array<string> = [];
// Try get the info needed from the RTCRtpReceiver. This is only available on chrome
if (!RTCRtpReceiver.getCapabilities) {
browserSupportedCodecs.push('Only available on Chrome');
return browserSupportedCodecs;
}
});
return browserSupportedCodecs;
})(),
const matcher = /(VP\d|H26\d|AV1).*/;
const codecs =
RTCRtpReceiver.getCapabilities('video').codecs;
codecs.forEach((codec) => {
const str =
codec.mimeType.split('/')[1] +
' ' +
(codec.sdpFmtpLine || '');
const match = matcher.exec(str);
if (match !== null) {
browserSupportedCodecs.push(str);
}
});
return browserSupportedCodecs;
})(),
useUrlParams
)
);
@ -253,7 +257,9 @@ export class Config {
Flags.AutoConnect,
'Auto connect to stream',
'Whether we should attempt to auto connect to the signalling server or show a click to start prompt.',
false,
settings && settings.hasOwnProperty(Flags.AutoConnect) ?
settings[Flags.AutoConnect] :
false,
useUrlParams
)
);
@ -264,7 +270,9 @@ export class Config {
Flags.AutoPlayVideo,
'Auto play video',
'When video is ready automatically start playing it as opposed to showing a play button.',
true,
settings && settings.hasOwnProperty(Flags.AutoPlayVideo) ?
settings[Flags.AutoPlayVideo] :
true,
useUrlParams
)
);
@ -275,7 +283,9 @@ export class Config {
Flags.BrowserSendOffer,
'Browser send offer',
'Browser will initiate the WebRTC handshake by sending the offer to the streamer',
false,
settings && settings.hasOwnProperty(Flags.BrowserSendOffer) ?
settings[Flags.BrowserSendOffer] :
false,
useUrlParams
)
);
@ -286,7 +296,9 @@ export class Config {
Flags.UseMic,
'Use microphone',
'Make browser request microphone access and open an input audio track.',
false,
settings && settings.hasOwnProperty(Flags.UseMic) ?
settings[Flags.UseMic] :
false,
useUrlParams
)
);
@ -297,7 +309,9 @@ export class Config {
Flags.StartVideoMuted,
'Start video muted',
'Video will start muted if true.',
false,
settings && settings.hasOwnProperty(Flags.StartVideoMuted) ?
settings[Flags.StartVideoMuted] :
false,
useUrlParams
)
);
@ -308,18 +322,9 @@ export class Config {
Flags.SuppressBrowserKeys,
'Suppress browser keys',
'Suppress certain browser keys that we use in UE, for example F5 to show shader complexity instead of refresh the page.',
true,
useUrlParams
)
);
this.flags.set(
Flags.PreferSFU,
new SettingFlag(
Flags.PreferSFU,
'Prefer SFU',
'Try to connect to the SFU instead of P2P.',
false,
settings && settings.hasOwnProperty(Flags.SuppressBrowserKeys) ?
settings[Flags.SuppressBrowserKeys] :
true,
useUrlParams
)
);
@ -330,7 +335,9 @@ export class Config {
Flags.IsQualityController,
'Is quality controller?',
'True if this peer controls stream quality',
true,
settings && settings.hasOwnProperty(Flags.IsQualityController) ?
settings[Flags.IsQualityController] :
true,
useUrlParams
)
);
@ -341,7 +348,9 @@ export class Config {
Flags.ForceMonoAudio,
'Force mono audio',
'Force browser to request mono audio in the SDP',
false,
settings && settings.hasOwnProperty(Flags.ForceMonoAudio) ?
settings[Flags.ForceMonoAudio] :
false,
useUrlParams
)
);
@ -352,7 +361,9 @@ export class Config {
Flags.ForceTURN,
'Force TURN',
'Only generate TURN/Relayed ICE candidates.',
false,
settings && settings.hasOwnProperty(Flags.ForceTURN) ?
settings[Flags.ForceTURN] :
false,
useUrlParams
)
);
@ -363,7 +374,9 @@ export class Config {
Flags.AFKDetection,
'AFK if idle',
'Timeout the experience if user is AFK for a period.',
false,
settings && settings.hasOwnProperty(Flags.AFKDetection) ?
settings[Flags.AFKDetection] :
false,
useUrlParams
)
);
@ -374,7 +387,9 @@ export class Config {
Flags.MatchViewportResolution,
'Match viewport resolution',
'Pixel Streaming will be instructed to dynamically resize the video stream to match the size of the video element.',
false,
settings && settings.hasOwnProperty(Flags.MatchViewportResolution) ?
settings[Flags.MatchViewportResolution] :
false,
useUrlParams
)
);
@ -385,7 +400,9 @@ export class Config {
Flags.HoveringMouseMode,
'Control Scheme: Locked Mouse',
'Either locked mouse, where the pointer is consumed by the video and locked to it, or hovering mouse, where the mouse is not consumed.',
false,
settings && settings.hasOwnProperty(Flags.HoveringMouseMode) ?
settings[Flags.HoveringMouseMode] :
false,
useUrlParams,
(isHoveringMouse: boolean, setting: SettingBase) => {
setting.label = `Control Scheme: ${isHoveringMouse ? 'Hovering' : 'Locked'} Mouse`;
@ -399,7 +416,9 @@ export class Config {
Flags.FakeMouseWithTouches,
'Fake mouse with touches',
'A single finger touch is converted into a mouse event. This allows a non-touch application to be controlled partially via a touch device.',
false,
settings && settings.hasOwnProperty(Flags.FakeMouseWithTouches) ?
settings[Flags.FakeMouseWithTouches] :
true,
useUrlParams
)
);
@ -410,7 +429,9 @@ export class Config {
Flags.KeyboardInput,
'Keyboard input',
'If enabled, send keyboard events to streamer',
true,
settings && settings.hasOwnProperty(Flags.KeyboardInput) ?
settings[Flags.KeyboardInput] :
true,
useUrlParams
)
);
@ -421,7 +442,9 @@ export class Config {
Flags.MouseInput,
'Mouse input',
'If enabled, send mouse events to streamer',
true,
settings && settings.hasOwnProperty(Flags.MouseInput) ?
settings[Flags.MouseInput] :
true,
useUrlParams
)
);
@ -432,7 +455,9 @@ export class Config {
Flags.TouchInput,
'Touch input',
'If enabled, send touch events to streamer',
true,
settings && settings.hasOwnProperty(Flags.TouchInput) ?
settings[Flags.TouchInput] :
true,
useUrlParams
)
);
@ -443,7 +468,9 @@ export class Config {
Flags.GamepadInput,
'Gamepad input',
'If enabled, send gamepad events to streamer',
true,
settings && settings.hasOwnProperty(Flags.GamepadInput) ?
settings[Flags.GamepadInput] :
true,
useUrlParams
)
);
@ -454,7 +481,22 @@ export class Config {
Flags.XRControllerInput,
'XR controller input',
'If enabled, send XR controller events to streamer',
true,
settings && settings.hasOwnProperty(Flags.XRControllerInput) ?
settings[Flags.XRControllerInput] :
true,
useUrlParams
)
);
this.flags.set(
Flags.WaitForStreamer,
new SettingFlag(
Flags.WaitForStreamer,
'Wait for streamer',
'Will continue trying to connect to the first streamer available.',
settings && settings.hasOwnProperty(Flags.WaitForStreamer) ?
settings[Flags.WaitForStreamer] :
true,
useUrlParams
)
);
@ -471,7 +513,9 @@ export class Config {
'The time (in seconds) it takes for the application to time out if AFK timeout is enabled.',
0 /*min*/,
600 /*max*/,
120 /*value*/,
settings && settings.hasOwnProperty(NumericParameters.AFKTimeoutSecs) ?
settings[NumericParameters.AFKTimeoutSecs] :
120, /*value*/
useUrlParams
)
);
@ -484,7 +528,9 @@ export class Config {
'Maximum number of reconnects the application will attempt when a streamer disconnects.',
0 /*min*/,
999 /*max*/,
3 /*value*/,
settings && settings.hasOwnProperty(NumericParameters.MaxReconnectAttempts) ?
settings[NumericParameters.MaxReconnectAttempts] :
3, /*value*/
useUrlParams
)
);
@ -497,7 +543,9 @@ export class Config {
'The lower bound for the quantization parameter (QP) of the encoder. 0 = Best quality, 51 = worst quality.',
0 /*min*/,
51 /*max*/,
0 /*value*/,
settings && settings.hasOwnProperty(NumericParameters.MinQP) ?
settings[NumericParameters.MinQP] :
0, /*value*/
useUrlParams
)
);
@ -510,7 +558,9 @@ export class Config {
'The upper bound for the quantization parameter (QP) of the encoder. 0 = Best quality, 51 = worst quality.',
0 /*min*/,
51 /*max*/,
51 /*value*/,
settings && settings.hasOwnProperty(NumericParameters.MaxQP) ?
settings[NumericParameters.MaxQP] :
51, /*value*/
useUrlParams
)
);
@ -523,7 +573,9 @@ export class Config {
'The maximum FPS that WebRTC will try to transmit frames at.',
1 /*min*/,
999 /*max*/,
60 /*value*/,
settings && settings.hasOwnProperty(NumericParameters.WebRTCFPS) ?
settings[NumericParameters.WebRTCFPS] :
60, /*value*/
useUrlParams
)
);
@ -536,7 +588,9 @@ export class Config {
'The minimum bitrate that WebRTC should use.',
0 /*min*/,
500000 /*max*/,
0 /*value*/,
settings && settings.hasOwnProperty(NumericParameters.WebRTCMinBitrate) ?
settings[NumericParameters.WebRTCMinBitrate] :
0, /*value*/
useUrlParams
)
);
@ -549,7 +603,24 @@ export class Config {
'The maximum bitrate that WebRTC should use.',
0 /*min*/,
500000 /*max*/,
0 /*value*/,
settings && settings.hasOwnProperty(NumericParameters.WebRTCMaxBitrate) ?
settings[NumericParameters.WebRTCMaxBitrate] :
0, /*value*/
useUrlParams
)
);
this.numericParameters.set(
NumericParameters.StreamerAutoJoinInterval,
new SettingNumber(
NumericParameters.StreamerAutoJoinInterval,
'Streamer Auto Join Interval (ms)',
'Delay between retries when waiting for an available streamer.',
500 /*min*/,
900000 /*max*/,
settings && settings.hasOwnProperty(NumericParameters.StreamerAutoJoinInterval) ?
settings[NumericParameters.StreamerAutoJoinInterval] :
3000, /*value*/
useUrlParams
)
);
@ -728,7 +799,13 @@ export class Config {
`Cannot set text setting called ${id} - it does not exist in the Config.enumParameters map.`
);
} else {
this.optionParameters.get(id).selected = settingValue;
const optionSetting = this.optionParameters.get(id);
const existingOptions = optionSetting.options;
if (!existingOptions.includes(settingValue)) {
existingOptions.push(settingValue);
optionSetting.options = existingOptions;
}
optionSetting.selected = settingValue;
}
}
@ -748,24 +825,24 @@ export class Config {
}
}
/**
* Set a subset of all settings in one function call.
*
* @param settings A (partial) list of settings to set
*/
setSettings(settings: Partial<AllSettings>) {
for (const key of Object.keys(settings)) {
if (isFlagId(key)) {
this.setFlagEnabled(key, settings[key]);
} else if (isNumericId(key)) {
this.setNumericSetting(key, settings[key]);
} else if (isTextId(key)) {
this.setTextSetting(key, settings[key]);
} else if (isOptionId(key)) {
this.setOptionSettingValue(key, settings[key]);
/**
* Set a subset of all settings in one function call.
*
* @param settings A (partial) list of settings to set
*/
setSettings(settings: Partial<AllSettings>) {
for (const key of Object.keys(settings)) {
if (isFlagId(key)) {
this.setFlagEnabled(key, settings[key]);
} else if (isNumericId(key)) {
this.setNumericSetting(key, settings[key]);
} else if (isTextId(key)) {
this.setTextSetting(key, settings[key]);
} else if (isOptionId(key)) {
this.setOptionSettingValue(key, settings[key]);
}
}
}
}
/**
* Get all settings

View File

@ -37,7 +37,7 @@ export class SettingNumber<
if (!useUrlParams || !urlParams.has(this.id)) {
this.number = defaultNumber;
} else {
const parsedValue = Number.parseInt(urlParams.get(this.id));
const parsedValue = Number.parseFloat(urlParams.get(this.id));
this.number = Number.isNaN(parsedValue)
? defaultNumber
: parsedValue;

View File

@ -24,7 +24,7 @@ export class SettingOption<
// eslint-disable-next-line @typescript-eslint/no-empty-function
defaultOnChangeListener: (changedValue: unknown, setting: SettingBase) => void = () => { /* Do nothing, to be overridden. */ }
) {
super(id, label, description, [defaultTextValue, defaultTextValue], defaultOnChangeListener);
super(id, label, description, defaultTextValue, defaultOnChangeListener);
this.options = options;
const urlParams = new URLSearchParams(window.location.search);
@ -103,11 +103,22 @@ export class SettingOption<
public set selected(value: string) {
// A user may not specify the full possible value so we instead use the closest match.
// eg ?xxx=H264 would select 'H264 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f'
const filteredList = this.options.filter(
let filteredList = this.options.filter(
(option: string) => option.indexOf(value) !== -1
);
if (filteredList.length) {
this.value = filteredList[0];
return;
}
// A user has specified a codec with a fmtp string but this codec + fmtp line isn't available.
// in that case, just use the codec
filteredList = this.options.filter(
(option: string) => option.indexOf(value.split(' ')[0]) !== -1
);
if (filteredList.length) {
this.value = filteredList[0];
return;
}
}
}

View File

@ -0,0 +1,129 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { Logger } from '../Logger/Logger';
import {
DataChannelLatencyTestRecord,
DataChannelLatencyTestRequest,
DataChannelLatencyTestResponse,
DataChannelLatencyTestResult,
DataChannelLatencyTestSeq,
DataChannelLatencyTestTimestamp
} from "./DataChannelLatencyTestResults";
export type DataChannelLatencyTestConfig = {
// test duration in milliseconds
duration: number;
//requests per second
rps: number;
//request filler size
requestSize: number;
//response filler size
responseSize: number;
}
export type DataChannelLatencyTestSink = (request: DataChannelLatencyTestRequest) => void;
export type DataChannelLatencyTestResultCallback = (result: DataChannelLatencyTestResult) => void;
export class DataChannelLatencyTestController {
startTime: DataChannelLatencyTestTimestamp;
sink: DataChannelLatencyTestSink;
callback: DataChannelLatencyTestResultCallback;
records: Map<DataChannelLatencyTestSeq, DataChannelLatencyTestRecord>;
seq: DataChannelLatencyTestSeq;
interval: NodeJS.Timer;
constructor(sink: DataChannelLatencyTestSink, callback: DataChannelLatencyTestResultCallback) {
this.sink = sink;
this.callback = callback;
this.records = new Map();
this.seq = 0;
}
start(config: DataChannelLatencyTestConfig) {
if (this.isRunning()) {
return false;
}
this.startTime = Date.now();
this.records.clear();
this.interval = setInterval((() => {
if (Date.now() - this.startTime >= config.duration) {
this.stop();
} else {
this.sendRequest(config.requestSize, config.responseSize);
}
}).bind(this), Math.floor(1000/config.rps));
return true;
}
stop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = undefined;
this.callback(this.produceResult());
}
}
produceResult(): DataChannelLatencyTestResult {
const resultRecords = new Map(this.records);
return {
records: resultRecords,
dataChannelRtt: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => {
return acc + (next.playerReceivedTimestamp - next.playerSentTimestamp);
}, 0) / this.records.size),
playerToStreamerTime: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => {
return acc + (next.streamerReceivedTimestamp - next.playerSentTimestamp);
}, 0) / this.records.size),
streamerToPlayerTime: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => {
return acc + (next.playerReceivedTimestamp - next.streamerSentTimestamp);
}, 0) / this.records.size),
exportLatencyAsCSV: () => {
let csv = "Timestamp;RTT;PlayerToStreamer;StreamerToPlayer;\n";
resultRecords.forEach((record) => {
csv += record.playerSentTimestamp + ";";
csv += (record.playerReceivedTimestamp - record.playerSentTimestamp) + ";";
csv += (record.streamerReceivedTimestamp - record.playerSentTimestamp) + ";";
csv += (record.playerReceivedTimestamp - record.streamerSentTimestamp) + ";";
csv += "\n";
})
return csv;
}
}
}
isRunning() {
return !!this.interval;
}
receive(response: DataChannelLatencyTestResponse) {
if (!this.isRunning()) {
return;
}
if (!response) {
Logger.Error(
Logger.GetStackTrace(),
"Undefined response from server"
);
return;
}
let record = this.records.get(response.Seq);
if (record) {
record.update(response);
}
}
sendRequest(requestSize: number, responseSize: number) {
let request = this.createRequest(requestSize, responseSize);
let record = new DataChannelLatencyTestRecord(request);
this.records.set(record.seq, record);
this.sink(request);
}
createRequest(requestSize: number, responseSize: number): DataChannelLatencyTestRequest {
return {
Seq: this.seq++,
FillResponseSize: responseSize,
Filler: requestSize ? "A".repeat(requestSize) : ""
}
}
}

View File

@ -0,0 +1,67 @@
// Copyright Epic Games, Inc. All Rights Reserved.
/**
* Data Channel Latency Test types
*/
/**
* Unix epoch
*/
export type DataChannelLatencyTestTimestamp = number;
/**
* Sequence number represented by unsigned int
*/
export type DataChannelLatencyTestSeq = number;
/**
* Request sent to Streamer
*/
export type DataChannelLatencyTestRequest = {
Seq: DataChannelLatencyTestSeq;
FillResponseSize: number;
Filler: string;
}
/**
* Response from the Streamer
*/
export type DataChannelLatencyTestResponse = {
Seq: DataChannelLatencyTestSeq;
Filler: string;
ReceivedTimestamp: DataChannelLatencyTestTimestamp;
SentTimestamp: DataChannelLatencyTestTimestamp;
}
export type DataChannelLatencyTestResult = {
records: Map<DataChannelLatencyTestSeq, DataChannelLatencyTestRecord>
dataChannelRtt: number,
playerToStreamerTime: number,
streamerToPlayerTime: number,
exportLatencyAsCSV: () => string
}
export class DataChannelLatencyTestRecord {
seq: DataChannelLatencyTestSeq;
playerSentTimestamp: DataChannelLatencyTestTimestamp;
playerReceivedTimestamp: DataChannelLatencyTestTimestamp;
streamerReceivedTimestamp: DataChannelLatencyTestTimestamp;
streamerSentTimestamp: DataChannelLatencyTestTimestamp;
requestFillerSize: number;
responseFillerSize: number;
constructor(request: DataChannelLatencyTestRequest) {
this.seq = request.Seq;
this.playerSentTimestamp = Date.now();
this.requestFillerSize = request.Filler ? request.Filler.length : 0;
}
update(response: DataChannelLatencyTestResponse) {
this.playerReceivedTimestamp = Date.now();
this.streamerReceivedTimestamp = response.ReceivedTimestamp;
this.streamerSentTimestamp = response.SentTimestamp;
this.responseFillerSize = response.Filler ? response.Filler.length : 0;
}
}

View File

@ -72,7 +72,7 @@ export class FakeTouchController implements ITouchController {
* @param touch - the activating touch event
*/
onTouchStart(touch: TouchEvent): void {
if (!this.videoElementProvider.isVideoReady()) {
if (!this.videoElementProvider.isVideoReady() || touch.target !== this.videoElementProvider.getVideoElement()) {
return;
}
if (this.fakeTouchFinger == null) {
@ -108,7 +108,7 @@ export class FakeTouchController implements ITouchController {
* @param touchEvent - the activating touch event
*/
onTouchEnd(touchEvent: TouchEvent): void {
if (!this.videoElementProvider.isVideoReady()) {
if (!this.videoElementProvider.isVideoReady() || this.fakeTouchFinger == null) {
return;
}
const videoElementParent =
@ -144,7 +144,7 @@ export class FakeTouchController implements ITouchController {
* @param touchEvent - the activating touch event
*/
onTouchMove(touchEvent: TouchEvent): void {
if (!this.videoElementProvider.isVideoReady()) {
if (!this.videoElementProvider.isVideoReady() || this.fakeTouchFinger == null) {
return;
}
const toStreamerHandlers =

View File

@ -28,6 +28,11 @@ export class GamePadController {
window.requestAnimationFrame
).bind(window);
const browserWindow = window as Window;
const onBeforeUnload = (ev: Event) =>
this.onBeforeUnload(ev);
window.addEventListener('beforeunload', onBeforeUnload);
if ('GamepadEvent' in browserWindow) {
const onGamePadConnected = (ev: GamepadEvent) =>
this.gamePadConnectHandler(ev);
@ -197,7 +202,8 @@ export class GamePadController {
} else {
toStreamerHandlers.get('GamepadButtonReleased')([
controllerIndex,
i
i,
0
]);
}
}
@ -253,6 +259,14 @@ export class GamePadController {
onGamepadDisconnected(controllerIdx: number) {
// Default Functionality: Do Nothing
}
onBeforeUnload(ev: Event) {
// When a user navigates away from the page, we need to inform UE of all the disconnecting
// controllers
for(const controller of this.controllers) {
this.onGamepadDisconnected(controller.id);
}
}
}

View File

@ -86,6 +86,7 @@ export class HoveringMouseEvents implements IMouseEvents {
if (!this.mouseController.videoElementProvider.isVideoReady()) {
return;
}
Logger.Log(Logger.GetStackTrace(), 'onMouse Up', 6);
const coord =
this.mouseController.coordinateConverter.normalizeAndQuantizeUnsigned(
mouseEvent.offsetX,
@ -102,25 +103,13 @@ export class HoveringMouseEvents implements IMouseEvents {
}
/**
* Handle the mouse context menu event, sends the mouse data to the UE Instance
* Consumes the mouse context event. The UE instance has no equivalent and doesn't need to be informed.
* @param mouseEvent - Mouse Event
*/
handleContextMenu(mouseEvent: MouseEvent) {
if (!this.mouseController.videoElementProvider.isVideoReady()) {
return;
}
const coord =
this.mouseController.coordinateConverter.normalizeAndQuantizeUnsigned(
mouseEvent.offsetX,
mouseEvent.offsetY
);
const toStreamerHandlers =
this.mouseController.toStreamerMessagesProvider.toStreamerHandlers;
toStreamerHandlers.get('MouseUp')([
mouseEvent.button,
coord.x,
coord.y
]);
mouseEvent.preventDefault();
}
@ -177,6 +166,7 @@ export class HoveringMouseEvents implements IMouseEvents {
if (!this.mouseController.videoElementProvider.isVideoReady()) {
return;
}
Logger.Log(Logger.GetStackTrace(), 'onMouse press', 6);
this.mouseController.pressMouseButtons(
mouseEvent.buttons,
mouseEvent.offsetX,
@ -192,6 +182,7 @@ export class HoveringMouseEvents implements IMouseEvents {
if (!this.mouseController.videoElementProvider.isVideoReady()) {
return;
}
Logger.Log(Logger.GetStackTrace(), 'onMouse release', 6);
this.mouseController.releaseMouseButtons(
mouseEvent.buttons,
mouseEvent.offsetX,

View File

@ -150,16 +150,21 @@ export class KeyboardController {
* Registers document keyboard events with the controller
*/
registerKeyBoardEvents() {
const compositionEndHandler = (ev: CompositionEvent) => this.handleOnCompositionEnd(ev);
const keyDownHandler = (ev: KeyboardEvent) => this.handleOnKeyDown(ev);
const keyUpHandler = (ev: KeyboardEvent) => this.handleOnKeyUp(ev);
const keyPressHandler = (ev: KeyboardEvent) => this.handleOnKeyPress(ev);
document.addEventListener("compositionend", compositionEndHandler);
document.addEventListener("keydown", keyDownHandler);
document.addEventListener("keyup", keyUpHandler);
//This has been deprecated as at Jun 13 2021
document.addEventListener("keypress", keyPressHandler);
this.keyboardEventListenerTracker.addUnregisterCallback(
() => document.removeEventListener("compositionend", compositionEndHandler)
);
this.keyboardEventListenerTracker.addUnregisterCallback(
() => document.removeEventListener("keydown", keyDownHandler)
);
@ -184,7 +189,7 @@ export class KeyboardController {
*/
handleOnKeyDown(keyboardEvent: KeyboardEvent) {
const keyCode = this.getKeycode(keyboardEvent);
if (!keyCode) {
if (!keyCode || keyCode === 229) {
return;
}
@ -232,10 +237,7 @@ export class KeyboardController {
Logger.Log(Logger.GetStackTrace(), `key up ${keyCode}`, 6);
const toStreamerHandlers =
this.toStreamerMessagesProvider.toStreamerHandlers;
toStreamerHandlers.get('KeyUp')([
keyCode,
keyboardEvent.repeat ? 1 : 0
]);
toStreamerHandlers.get('KeyUp')([ keyCode ]);
if (
this.config.isFlagEnabled(Flags.SuppressBrowserKeys) &&
@ -266,6 +268,37 @@ export class KeyboardController {
toStreamerHandlers.get('KeyPress')([charCode]);
}
/**
* Handle whenever composition ends (eg chinese simplified)
* @param compositionEvent - the composition event
*/
handleOnCompositionEnd(compositionEvent: CompositionEvent) {
if (compositionEvent.data && compositionEvent.data.length) {
compositionEvent.data.split('').forEach((char) => {
// This keydown, keypress, keyup flow is required to mimic the way characters are
// normally triggered
this.handleOnKeyDown(
new KeyboardEvent('keydown', {
keyCode: char.toUpperCase().charCodeAt(0),
charCode: char.charCodeAt(0)
})
);
this.handleOnKeyPress(
new KeyboardEvent('keypress', {
keyCode: char.toUpperCase().charCodeAt(0),
charCode: char.charCodeAt(0)
})
);
this.handleOnKeyUp(
new KeyboardEvent('keyup', {
keyCode: char.toUpperCase().charCodeAt(0),
charCode: char.charCodeAt(0)
})
);
});
}
}
/**
* Gets the Keycode of the Key pressed
* @param keyboardEvent - Key board Event

View File

@ -152,16 +152,15 @@ export class TouchController implements ITouchController {
if (!this.videoElementProvider.isVideoReady()) {
return;
}
const videoElementParent =
this.videoElementProvider.getVideoParentElement();
const offset = this.videoElementProvider.getVideoParentElement().getBoundingClientRect();
const toStreamerHandlers =
this.toStreamerMessagesProvider.toStreamerHandlers;
for (let t = 0; t < touches.length; t++) {
const numTouches = 1; // the number of touches to be sent this message
const touch = touches[t];
const x = touch.clientX - videoElementParent.offsetLeft;
const y = touch.clientY - videoElementParent.offsetTop;
const x = touch.clientX - offset.left;
const y = touch.clientY - offset.top;
Logger.Log(
Logger.GetStackTrace(),
`F${this.fingerIds.get(touch.identifier)}=(${x}, ${y})`,
@ -179,7 +178,7 @@ export class TouchController implements ITouchController {
coord.x,
coord.y,
this.fingerIds.get(touch.identifier),
this.maxByteValue * touch.force,
this.maxByteValue * (touch.force > 0 ? touch.force : 1),
coord.inRange ? 1 : 0
]);
break;
@ -199,7 +198,7 @@ export class TouchController implements ITouchController {
coord.x,
coord.y,
this.fingerIds.get(touch.identifier),
this.maxByteValue * touch.force,
this.maxByteValue * (touch.force > 0 ? touch.force : 1),
coord.inRange ? 1 : 0
]);
break;

View File

@ -25,7 +25,7 @@ export class AggregatedStats {
inboundAudioStats: InboundAudioStats;
lastVideoStats: InboundVideoStats;
lastAudioStats: InboundAudioStats;
candidatePair: CandidatePairStats;
candidatePairs: Array<CandidatePairStats>;
DataChannelStats: DataChannelStats;
localCandidates: Array<CandidateStat>;
remoteCandidates: Array<CandidateStat>;
@ -37,7 +37,6 @@ export class AggregatedStats {
constructor() {
this.inboundVideoStats = new InboundVideoStats();
this.inboundAudioStats = new InboundAudioStats();
this.candidatePair = new CandidatePairStats();
this.DataChannelStats = new DataChannelStats();
this.outBoundVideoStats = new OutBoundVideoStats();
this.sessionStats = new SessionStats();
@ -52,6 +51,7 @@ export class AggregatedStats {
processStats(rtcStatsReport: RTCStatsReport) {
this.localCandidates = new Array<CandidateStat>();
this.remoteCandidates = new Array<CandidateStat>();
this.candidatePairs = new Array<CandidatePairStats>();
rtcStatsReport.forEach((stat) => {
const type: RTCStatsTypePS = stat.type;
@ -120,16 +120,10 @@ export class AggregatedStats {
* @param stat - the stats coming in from ice candidates
*/
handleCandidatePair(stat: CandidatePairStats) {
this.candidatePair.bytesReceived = stat.bytesReceived;
this.candidatePair.bytesSent = stat.bytesSent;
this.candidatePair.localCandidateId = stat.localCandidateId;
this.candidatePair.remoteCandidateId = stat.remoteCandidateId;
this.candidatePair.nominated = stat.nominated;
this.candidatePair.readable = stat.readable;
this.candidatePair.selected = stat.selected;
this.candidatePair.writable = stat.writable;
this.candidatePair.state = stat.state;
this.candidatePair.currentRoundTripTime = stat.currentRoundTripTime;
// Add the candidate pair to the candidate pair array
this.candidatePairs.push(stat)
}
/**
@ -162,6 +156,8 @@ export class AggregatedStats {
localCandidate.protocol = stat.protocol;
localCandidate.candidateType = stat.candidateType;
localCandidate.id = stat.id;
localCandidate.relayProtocol = stat.relayProtocol;
localCandidate.transportId = stat.transportId;
this.localCandidates.push(localCandidate);
}
@ -171,12 +167,14 @@ export class AggregatedStats {
*/
handleRemoteCandidate(stat: CandidateStat) {
const RemoteCandidate = new CandidateStat();
RemoteCandidate.label = 'local-candidate';
RemoteCandidate.label = 'remote-candidate';
RemoteCandidate.address = stat.address;
RemoteCandidate.port = stat.port;
RemoteCandidate.protocol = stat.protocol;
RemoteCandidate.id = stat.id;
RemoteCandidate.candidateType = stat.candidateType;
RemoteCandidate.relayProtocol = stat.relayProtocol;
RemoteCandidate.transportId = stat.transportId
this.remoteCandidates.push(RemoteCandidate);
}
@ -308,4 +306,12 @@ export class AggregatedStats {
isNumber(value: unknown): boolean {
return typeof value === 'number' && isFinite(value);
}
/**
* Helper function to return the active candidate pair
* @returns The candidate pair that is currently receiving data
*/
public getActiveCandidatePair(): CandidatePairStats | null {
return this.candidatePairs.find((candidatePair) => candidatePair.bytesReceived > 0, null)
}
}

View File

@ -6,12 +6,19 @@
export class CandidatePairStats {
bytesReceived: number;
bytesSent: number;
currentRoundTripTime: number;
id: string;
lastPacketReceivedTimestamp: number;
lastPacketSentTimestamp: number;
localCandidateId: string;
remoteCandidateId: string;
nominated: boolean;
priority: number;
readable: boolean;
writable: boolean;
remoteCandidateId: string;
selected: boolean;
state: string;
currentRoundTripTime: number;
timestamp: number;
transportId: string;
type: string;
writable: boolean;
}

View File

@ -4,10 +4,12 @@
* ICE Candidate Stat collected from the RTC Stats Report
*/
export class CandidateStat {
label: string;
id: string;
address: string;
candidateType: string;
id: string;
label: string;
port: number;
protocol: 'tcp' | 'udp';
relayProtocol: 'tcp' | 'udp' | 'tls';
transportId: string;
}

View File

@ -4,6 +4,7 @@ import { Logger } from '../Logger/Logger';
import { Config, OptionParameters, Flags } from '../Config/Config';
import { AggregatedStats } from './AggregatedStats';
import { parseRtpParameters, splitSections } from 'sdp';
import { RTCUtils } from '../Util/RTCUtils';
/**
* Handles the Peer Connection
@ -179,7 +180,7 @@ export class PeerConnectionController {
this.onVideoStats(this.aggregatedStats);
// Update the preferred codec selection based on what was actually negotiated
if (this.updateCodecSelection) {
if (this.updateCodecSelection && !!this.aggregatedStats.inboundVideoStats.codecId) {
this.config.setOptionSettingValue(
OptionParameters.PreferredCodec,
this.aggregatedStats.codecs.get(
@ -207,16 +208,13 @@ export class PeerConnectionController {
* @returns A modified Session Descriptor
*/
mungeSDP(sdp: string, useMic: boolean) {
const mungedSDP = sdp;
mungedSDP.replace(
let mungedSDP = sdp.replace(
/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm,
'$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r\n'
);
let audioSDP = '';
// set max bitrate to highest bitrate Opus supports
audioSDP += 'maxaveragebitrate=510000;';
let audioSDP = 'maxaveragebitrate=510000;';
if (useMic) {
// set the max capture rate to 48khz (so we can send high quality audio from mic)
@ -232,7 +230,7 @@ export class PeerConnectionController {
audioSDP += 'useinbandfec=1';
// We use the line 'useinbandfec=1' (which Opus uses) to set our Opus specific audio parameters.
mungedSDP.replace('useinbandfec=1', audioSDP);
mungedSDP = mungedSDP.replace('useinbandfec=1', audioSDP);
return mungedSDP;
}
@ -375,7 +373,9 @@ export class PeerConnectionController {
transceiver &&
transceiver.receiver &&
transceiver.receiver.track &&
transceiver.receiver.track.kind === 'video'
transceiver.receiver.track.kind === 'video' &&
// As of 06/2023, FireFox has added RTCRtpReceiver.getCapabilities, but hasn't added the ability to set codec preferences
transceiver.setCodecPreferences
) {
const preferredRTPCodec = this.preferredCodec.split(' ');
const codecs = [
@ -426,18 +426,16 @@ export class PeerConnectionController {
});
} else {
// set the audio options based on mic usage
const audioOptions = useMic
? {
autoGainControl: false,
channelCount: 1,
echoCancellation: false,
latency: 0,
noiseSuppression: false,
sampleRate: 48000,
sampleSize: 16,
volume: 1.0
}
: false;
const audioOptions = {
autoGainControl: false,
channelCount: 1,
echoCancellation: false,
latency: 0,
noiseSuppression: false,
sampleRate: 48000,
sampleSize: 16,
volume: 1.0
}
// set the media send options
const mediaSendOptions: MediaStreamConstraints = {
@ -452,12 +450,7 @@ export class PeerConnectionController {
if (stream) {
if (hasTransceivers) {
for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
if (
transceiver &&
transceiver.receiver &&
transceiver.receiver.track &&
transceiver.receiver.track.kind === 'audio'
) {
if (RTCUtils.canTransceiverReceiveAudio(transceiver)) {
for (const track of stream.getTracks()) {
if (track.kind && track.kind == 'audio') {
transceiver.sender.replaceTrack(track);

View File

@ -274,7 +274,8 @@ describe('PixelStreaming', () => {
type: MessageRecvTypes.STREAMER_LIST,
ids: streamerIdList
}),
autoSelectedStreamerId: streamerId
autoSelectedStreamerId: streamerId,
wantedStreamerId: null
}));
expect(webSocketSpyFunctions.sendSpy).toHaveBeenCalledWith(
expect.stringMatching(/"type":"subscribe".*MOCK_PIXEL_STREAMING/)
@ -298,7 +299,8 @@ describe('PixelStreaming', () => {
type: MessageRecvTypes.STREAMER_LIST,
ids: extendedStreamerIdList
}),
autoSelectedStreamerId: null
autoSelectedStreamerId: null,
wantedStreamerId: null
}));
expect(webSocketSpyFunctions.sendSpy).not.toHaveBeenCalledWith(
expect.stringMatching(/"type":"subscribe"/)
@ -396,9 +398,9 @@ describe('PixelStreaming', () => {
expect.objectContaining({
data: {
aggregatedStats: expect.objectContaining({
candidatePair: expect.objectContaining({
bytesReceived: 123
}),
candidatePairs: [
expect.objectContaining({ bytesReceived: 123 })
],
localCandidates: [
expect.objectContaining({ address: 'mock-address' })
]

View File

@ -25,10 +25,25 @@ import {
WebRtcConnectingEvent,
WebRtcDisconnectedEvent,
WebRtcFailedEvent,
WebRtcSdpEvent
WebRtcSdpEvent,
DataChannelLatencyTestResponseEvent,
DataChannelLatencyTestResultEvent,
PlayerCountEvent,
WebRtcTCPRelayDetectedEvent
} from '../Util/EventEmitter';
import { MessageOnScreenKeyboard } from '../WebSockets/MessageReceive';
import { WebXRController } from '../WebXR/WebXRController';
import { MessageDirection } from '../UeInstanceMessage/StreamMessageController';
import {
DataChannelLatencyTestConfig,
DataChannelLatencyTestController
} from "../DataChannel/DataChannelLatencyTestController";
import {
DataChannelLatencyTestResponse,
DataChannelLatencyTestResult
} from "../DataChannel/DataChannelLatencyTestResults";
import { RTCUtils } from '../Util/RTCUtils';
export interface PixelStreamingOverrides {
/** The DOM elment where Pixel Streaming video and user input event handlers are attached to.
@ -47,6 +62,8 @@ export interface PixelStreamingOverrides {
export class PixelStreaming {
protected _webRtcController: WebRtcPlayerController;
protected _webXrController: WebXRController;
protected _dataChannelLatencyTestController: DataChannelLatencyTestController;
/**
* Configuration object. You can read or modify config through this object. Whenever
* the configuration is changed, the library will emit a `settingsChanged` event.
@ -55,7 +72,6 @@ export class PixelStreaming {
private _videoElementParent: HTMLElement;
_showActionOrErrorOnDisconnect = true;
private allowConsoleCommands = false;
private onScreenKeyboardHelper: OnScreenKeyboard;
@ -102,6 +118,15 @@ export class PixelStreaming {
this.onScreenKeyboardHelper.showOnScreenKeyboard(command);
this._webXrController = new WebXRController(this._webRtcController);
this._setupWebRtcTCPRelayDetection = this._setupWebRtcTCPRelayDetection.bind(this)
// Add event listener for the webRtcConnected event
this._eventEmitter.addEventListener("webRtcConnected", (webRtcConnectedEvent: WebRtcConnectedEvent) => {
// Bind to the stats received event
this._eventEmitter.addEventListener("statsReceived", this._setupWebRtcTCPRelayDetection);
});
}
/**
@ -338,7 +363,7 @@ export class PixelStreaming {
*/
public reconnect() {
this._eventEmitter.dispatchEvent(new StreamReconnectEvent());
this._webRtcController.restartStreamAutomatically();
this._webRtcController.tryReconnect("Reconnecting...");
}
/**
@ -369,12 +394,62 @@ export class PixelStreaming {
}
}
/**
* Will unmute the microphone track which is sent to Unreal Engine.
* By default, will only unmute an existing mic track.
*
* @param forceEnable Can be used for cases when this object wasn't initialized with a mic track.
* If this parameter is true, the connection will be restarted with a microphone.
* Warning: this takes some time, as a full renegotiation and reconnection will happen.
*/
public unmuteMicrophone(forceEnable = false) : void {
// If there's an existing mic track, we just set muted state
if (this.config.isFlagEnabled('UseMic')) {
this.setMicrophoneMuted(false);
return;
}
// If there's no pre-existing mic track, and caller is ok with full reset, we enable and reset
if (forceEnable) {
this.config.setFlagEnabled("UseMic", true);
this.reconnect();
return;
}
// If we prefer not to force a reconnection, just warn the user that this operation didn't happen
Logger.Warning(
Logger.GetStackTrace(),
'Trying to unmute mic, but PixelStreaming was initialized with no microphone track. Call with forceEnable == true to re-connect with a mic track.'
);
}
public muteMicrophone() : void {
if (this.config.isFlagEnabled('UseMic')) {
this.setMicrophoneMuted(true);
return;
}
// If there wasn't a mic track, just let user know there's nothing to mute
Logger.Info(
Logger.GetStackTrace(),
'Trying to mute mic, but PixelStreaming has no microphone track, so sending sound is already disabled.'
);
}
private setMicrophoneMuted(mute: boolean) : void
{
for (const transceiver of this._webRtcController?.peerConnectionController?.peerConnection?.getTransceivers() ?? []) {
if (RTCUtils.canTransceiverSendAudio(transceiver)) {
transceiver.sender.track.enabled = !mute;
}
}
}
/**
* Emit an event on auto connecting
*/
_onWebRtcAutoConnect() {
this._eventEmitter.dispatchEvent(new WebRtcAutoConnectEvent());
this._showActionOrErrorOnDisconnect = true;
}
/**
@ -394,30 +469,16 @@ export class PixelStreaming {
/**
* Event fired when the video is disconnected - emits given eventString or an override
* message from webRtcController if one has been set
* @param eventString - the event text that will be emitted
* @param eventString - a string describing why the connection closed
* @param allowClickToReconnect - true if we want to allow the user to retry the connection with a click
*/
_onDisconnect(eventString: string) {
// if we have overridden the default disconnection message, assign the new value here
if (
this._webRtcController.getDisconnectMessageOverride() != '' &&
this._webRtcController.getDisconnectMessageOverride() !==
undefined &&
this._webRtcController.getDisconnectMessageOverride() != null
) {
eventString = this._webRtcController.getDisconnectMessageOverride();
this._webRtcController.setDisconnectMessageOverride('');
}
_onDisconnect(eventString: string, allowClickToReconnect: boolean) {
this._eventEmitter.dispatchEvent(
new WebRtcDisconnectedEvent({
eventString,
showActionOrErrorOnDisconnect:
this._showActionOrErrorOnDisconnect
eventString: eventString,
allowClickToReconnect: allowClickToReconnect
})
);
if (this._showActionOrErrorOnDisconnect == false) {
this._showActionOrErrorOnDisconnect = true;
}
}
/**
@ -459,6 +520,12 @@ export class PixelStreaming {
);
}
_onDataChannelLatencyTestResponse(response: DataChannelLatencyTestResponse) {
this._eventEmitter.dispatchEvent(
new DataChannelLatencyTestResponseEvent({ response })
);
}
/**
* Set up functionality to happen when receiving video statistics
* @param videoStats - video statistics as a aggregate stats object
@ -510,12 +577,16 @@ export class PixelStreaming {
const useUrlParams = this.config.useUrlParams;
const urlParams = new URLSearchParams(window.location.search);
Logger.Info(
Logger.GetStackTrace(),
`using URL parameters ${useUrlParams}`
);
if (settings.EncoderSettings) {
this.config.setNumericSetting(
NumericParameters.MinQP,
// If a setting is set in the URL, make sure we respect that value as opposed to what the application sends us
(useUrlParams && urlParams.has(NumericParameters.MinQP))
? Number.parseInt(urlParams.get(NumericParameters.MinQP))
? Number.parseFloat(urlParams.get(NumericParameters.MinQP))
: settings.EncoderSettings.MinQP
);
@ -523,7 +594,7 @@ export class PixelStreaming {
this.config.setNumericSetting(
NumericParameters.MaxQP,
(useUrlParams && urlParams.has(NumericParameters.MaxQP))
? Number.parseInt(urlParams.get(NumericParameters.MaxQP))
? Number.parseFloat(urlParams.get(NumericParameters.MaxQP))
: settings.EncoderSettings.MaxQP
);
}
@ -531,20 +602,20 @@ export class PixelStreaming {
this.config.setNumericSetting(
NumericParameters.WebRTCMinBitrate,
(useUrlParams && urlParams.has(NumericParameters.WebRTCMinBitrate))
? Number.parseInt(urlParams.get(NumericParameters.WebRTCMinBitrate)) / 1000 /* bps to kbps */
: settings.WebRTCSettings.MinBitrate / 1000 /* bps to kbps */
? Number.parseFloat(urlParams.get(NumericParameters.WebRTCMinBitrate))
: (settings.WebRTCSettings.MinBitrate / 1000) /* bps to kbps */
);
this.config.setNumericSetting(
NumericParameters.WebRTCMaxBitrate,
(useUrlParams && urlParams.has(NumericParameters.WebRTCMaxBitrate))
? Number.parseInt(urlParams.get(NumericParameters.WebRTCMaxBitrate)) / 1000 /* bps to kbps */
: settings.WebRTCSettings.MaxBitrate / 1000 /* bps to kbps */
? Number.parseFloat(urlParams.get(NumericParameters.WebRTCMaxBitrate))
: (settings.WebRTCSettings.MaxBitrate / 1000) /* bps to kbps */
);
this.config.setNumericSetting(
NumericParameters.WebRTCFPS,
(useUrlParams && urlParams.has(NumericParameters.WebRTCFPS))
? Number.parseInt(urlParams.get(NumericParameters.WebRTCFPS))
? Number.parseFloat(urlParams.get(NumericParameters.WebRTCFPS))
: settings.WebRTCSettings.FPS
);
}
@ -561,6 +632,34 @@ export class PixelStreaming {
);
}
_onPlayerCount(playerCount: number) {
this._eventEmitter.dispatchEvent(
new PlayerCountEvent({ count: playerCount })
);
}
// Sets up to emit the webrtc tcp relay detect event
_setupWebRtcTCPRelayDetection(statsReceivedEvent: StatsReceivedEvent) {
// Get the active candidate pair
let activeCandidatePair = statsReceivedEvent.data.aggregatedStats.getActiveCandidatePair();
// Check if the active candidate pair is not null
if (activeCandidatePair != null) {
// Get the local candidate assigned to the active candidate pair
let localCandidate = statsReceivedEvent.data.aggregatedStats.localCandidates.find((candidate) => candidate.id == activeCandidatePair.localCandidateId, null)
// Check if the local candidate is not null, candidate type is relay and the relay protocol is tcp
if (localCandidate != null && localCandidate.candidateType == 'relay' && localCandidate.relayProtocol == 'tcp') {
// Send the web rtc tcp relay detected event
this._eventEmitter.dispatchEvent(new WebRtcTCPRelayDetectedEvent());
}
// The check is completed and the stats listen event can be removed
this._eventEmitter.removeEventListener("statsReceived", this._setupWebRtcTCPRelayDetection);
}
}
/**
* Request a connection latency test.
* NOTE: There are plans to refactor all request* functions. Expect changes if you use this!
@ -574,6 +673,30 @@ export class PixelStreaming {
return true;
}
/**
* Request a data channel latency test.
* NOTE: There are plans to refactor all request* functions. Expect changes if you use this!
*/
public requestDataChannelLatencyTest(config: DataChannelLatencyTestConfig) {
if (!this._webRtcController.videoPlayer.isVideoReady()) {
return false;
}
if (!this._dataChannelLatencyTestController) {
this._dataChannelLatencyTestController = new DataChannelLatencyTestController(
this._webRtcController.sendDataChannelLatencyTest.bind(this._webRtcController),
(result: DataChannelLatencyTestResult) => {
this._eventEmitter.dispatchEvent(new DataChannelLatencyTestResultEvent( { result }))
});
this.addEventListener(
"dataChannelLatencyTestResponse",
({data: {response} }) => {
this._dataChannelLatencyTestController.receive(response);
}
)
}
return this._dataChannelLatencyTestController.start(config);
}
/**
* Request for the UE application to show FPS counter.
* NOTE: There are plans to refactor all request* functions. Expect changes if you use this!
@ -725,4 +848,37 @@ export class PixelStreaming {
public get webXrController() {
return this._webXrController;
}
public registerMessageHandler(name: string, direction: MessageDirection, handler?: (data: ArrayBuffer | Array<number | string>) => void) {
if(direction === MessageDirection.FromStreamer && typeof handler === 'undefined') {
Logger.Warning(Logger.GetStackTrace(), `Unable to register an undefined handler for ${name}`)
return;
}
if(direction === MessageDirection.ToStreamer && typeof handler === 'undefined') {
this._webRtcController.streamMessageController.registerMessageHandler(
direction,
name,
(data: Array<number | string>) =>
this._webRtcController.sendMessageController.sendMessageToStreamer(
name,
data
)
);
} else {
this._webRtcController.streamMessageController.registerMessageHandler(
direction,
name,
(data: ArrayBuffer) => handler(data)
);
}
}
public get toStreamerHandlers() {
return this._webRtcController.streamMessageController.toStreamerHandlers;
}
public isReconnecting() {
return this._webRtcController.isReconnecting;
}
}

View File

@ -1,87 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { DataChannelSender } from '../DataChannel/DataChannelSender';
import { Logger } from '../Logger/Logger';
import { StreamMessageController } from './StreamMessageController';
export class SendDescriptorController {
toStreamerMessagesMapProvider: StreamMessageController;
dataChannelSender: DataChannelSender;
constructor(
dataChannelSender: DataChannelSender,
toStreamerMessagesMapProvider: StreamMessageController
) {
this.dataChannelSender = dataChannelSender;
this.toStreamerMessagesMapProvider = toStreamerMessagesMapProvider;
}
/**
* Send a Latency Test to the UE Instance
* @param descriptor - the descriptor for a latency test
*/
sendLatencyTest(descriptor: object) {
this.sendDescriptor('LatencyTest', descriptor);
}
/**
* Send a Latency Test to the UE Instance
* @param descriptor - the descriptor for a command
*/
emitCommand(descriptor: object) {
this.sendDescriptor('Command', descriptor);
}
/**
* Send a Latency Test to the UE Instance
* @param descriptor - the descriptor for a UI Interaction
*/
emitUIInteraction(descriptor: object | string) {
this.sendDescriptor('UIInteraction', descriptor);
}
/**
* Send a Descriptor to the UE Instances
* @param messageType - UE Message Type
* @param descriptor - Descriptor Message as JSON
*/
sendDescriptor(messageType: string, descriptor: object | string) {
// Convert the descriptor object into a JSON string.
const descriptorAsString = JSON.stringify(descriptor);
const toStreamerMessages =
this.toStreamerMessagesMapProvider.toStreamerMessages;
const messageFormat = toStreamerMessages.getFromKey(messageType);
if (messageFormat === undefined) {
Logger.Error(
Logger.GetStackTrace(),
`Attempted to emit descriptor with message type: ${messageType}, but the frontend hasn't been configured to send such a message. Check you've added the message type in your cpp`
);
}
Logger.Log(Logger.GetStackTrace(), 'Sending: ' + descriptor, 6);
// Add the UTF-16 JSON string to the array byte buffer, going two bytes at
// a time.
const data = new DataView(
new ArrayBuffer(1 + 2 + 2 * descriptorAsString.length)
);
let byteIdx = 0;
data.setUint8(byteIdx, messageFormat.id);
byteIdx++;
data.setUint16(byteIdx, descriptorAsString.length, true);
byteIdx += 2;
for (let i = 0; i < descriptorAsString.length; i++) {
data.setUint16(byteIdx, descriptorAsString.charCodeAt(i), true);
byteIdx += 2;
}
if (!this.dataChannelSender.canSend()) {
Logger.Info(
Logger.GetStackTrace(),
`Data channel cannot send yet, skipping sending descriptor message: ${messageType} - ${descriptorAsString}`
);
return;
}
this.dataChannelSender.sendData(data.buffer);
}
}

View File

@ -26,14 +26,14 @@ export class SendMessageController {
* @param messageData - the message data we are sending over the data channel
* @returns - nil
*/
sendMessageToStreamer(messageType: string, messageData?: Array<number>) {
sendMessageToStreamer(messageType: string, messageData?: Array<number | string>) {
if (messageData === undefined) {
messageData = [];
}
const toStreamerMessages =
this.toStreamerMessagesMapProvider.toStreamerMessages;
const messageFormat = toStreamerMessages.getFromKey(messageType);
const messageFormat = toStreamerMessages.get(messageType);
if (messageFormat === undefined) {
Logger.Error(
Logger.GetStackTrace(),
@ -42,39 +42,100 @@ export class SendMessageController {
return;
}
const data = new DataView(
new ArrayBuffer(messageFormat.byteLength + 1)
);
data.setUint8(0, messageFormat.id);
let byteOffset = 1;
if(messageFormat.structure && messageData && messageFormat.structure.length !== messageData.length) {
Logger.Error(
Logger.GetStackTrace(),
`Provided message data doesn't match expected layout. Expected [ ${messageFormat.structure.map((element: string) => {
switch (element) {
case 'uint8':
case 'uint16':
case 'int16':
case 'float':
case 'double':
return 'number';
case 'string':
return 'string';
}
}).toString() } ] but received [ ${messageData.map((element: number | string) => typeof element).toString()} ]`
);
return;
}
messageData.forEach((element: number, idx: number) => {
let byteLength = 0;
const textEncoder = new TextEncoder();
// One loop to calculate the length in bytes of all of the provided data
messageData.forEach((element: number | string, idx: number) => {
const type = messageFormat.structure[idx];
switch (type) {
case 'uint8':
data.setUint8(byteOffset, element);
byteLength += 1;
break;
case 'uint16':
byteLength += 2;
break;
case 'int16':
byteLength += 2;
break;
case 'float':
byteLength += 4;
break;
case 'double':
byteLength += 8;
break;
case 'string':
// 2 bytes for string length
byteLength += 2;
// 2 bytes per characters
byteLength += 2 * textEncoder.encode(element as string).length;
break;
}
});
const data = new DataView(new ArrayBuffer(byteLength + 1));
data.setUint8(0, messageFormat.id);
let byteOffset = 1;
messageData.forEach((element: number | string, idx: number) => {
const type = messageFormat.structure[idx];
switch (type) {
case 'uint8':
data.setUint8(byteOffset, element as number);
byteOffset += 1;
break;
case 'uint16':
data.setUint16(byteOffset, element, true);
data.setUint16(byteOffset, element as number, true);
byteOffset += 2;
break;
case 'int16':
data.setInt16(byteOffset, element, true);
data.setInt16(byteOffset, element as number, true);
byteOffset += 2;
break;
case 'float':
data.setFloat32(byteOffset, element, true);
data.setFloat32(byteOffset, element as number, true);
byteOffset += 4;
break;
case 'double':
data.setFloat64(byteOffset, element, true);
data.setFloat64(byteOffset, element as number, true);
byteOffset += 8;
break;
case 'string':
data.setUint16(byteOffset, (element as string).length, true);
byteOffset += 2;
for (let i = 0; i < (element as string).length; i++) {
data.setUint16(byteOffset, (element as string).charCodeAt(i), true);
byteOffset += 2;
}
break;
}
});
@ -86,8 +147,8 @@ export class SendMessageController {
)}`
);
return;
} else {
this.dataChannelSender.sendData(data.buffer);
}
this.dataChannelSender.sendData(data.buffer);
}
}

View File

@ -1,33 +1,31 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { TwoWayMap } from './TwoWayMap';
import { Logger } from '../Logger/Logger';
export class ToStreamerMessage {
id: number;
byteLength: number;
structure?: Array<string>;
}
export class StreamMessageController {
toStreamerHandlers: Map<
string,
(messageData?: Array<number> | undefined) => void
(messageData?: Array<number | string> | undefined) => void
>;
fromStreamerHandlers: Map<
string,
(messageType: string, messageData?: ArrayBuffer | undefined) => void
>;
// Type Format
toStreamerMessages: TwoWayMap<string, ToStreamerMessage>;
// Type ID
fromStreamerMessages: TwoWayMap<string, number>;
// Type Format
toStreamerMessages: Map<string, ToStreamerMessage>;
// ID Type
fromStreamerMessages: Map<number, string>;
constructor() {
this.toStreamerHandlers = new Map();
this.fromStreamerHandlers = new Map();
this.toStreamerMessages = new TwoWayMap();
this.fromStreamerMessages = new TwoWayMap();
this.toStreamerMessages = new Map();
this.fromStreamerMessages = new Map();
}
/**
@ -37,190 +35,166 @@ export class StreamMessageController {
/*
* Control Messages. Range = 0..49.
*/
this.toStreamerMessages.add('IFrameRequest', {
this.toStreamerMessages.set('IFrameRequest', {
id: 0,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('RequestQualityControl', {
this.toStreamerMessages.set('RequestQualityControl', {
id: 1,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('FpsRequest', {
this.toStreamerMessages.set('FpsRequest', {
id: 2,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('AverageBitrateRequest', {
this.toStreamerMessages.set('AverageBitrateRequest', {
id: 3,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('StartStreaming', {
this.toStreamerMessages.set('StartStreaming', {
id: 4,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('StopStreaming', {
this.toStreamerMessages.set('StopStreaming', {
id: 5,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('LatencyTest', {
this.toStreamerMessages.set('LatencyTest', {
id: 6,
byteLength: 0,
structure: []
structure: ['string']
});
this.toStreamerMessages.add('RequestInitialSettings', {
this.toStreamerMessages.set('RequestInitialSettings', {
id: 7,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('TestEcho', {
this.toStreamerMessages.set('TestEcho', {
id: 8,
byteLength: 0,
structure: []
});
this.toStreamerMessages.set('DataChannelLatencyTest', {
id: 9,
structure: []
});
/*
* Input Messages. Range = 50..89.
*/
// Generic Input Messages. Range = 50..59.
this.toStreamerMessages.add('UIInteraction', {
this.toStreamerMessages.set('UIInteraction', {
id: 50,
byteLength: 0,
structure: []
structure: ['string']
});
this.toStreamerMessages.add('Command', {
this.toStreamerMessages.set('Command', {
id: 51,
byteLength: 0,
structure: []
structure: ['string']
});
// Keyboard Input Message. Range = 60..69.
this.toStreamerMessages.add('KeyDown', {
this.toStreamerMessages.set('KeyDown', {
id: 60,
byteLength: 2,
// keyCode isRepeat
structure: ['uint8', 'uint8']
});
this.toStreamerMessages.add('KeyUp', {
this.toStreamerMessages.set('KeyUp', {
id: 61,
byteLength: 1,
// keyCode
structure: ['uint8']
});
this.toStreamerMessages.add('KeyPress', {
this.toStreamerMessages.set('KeyPress', {
id: 62,
byteLength: 2,
// charcode
structure: ['uint16']
});
// Mouse Input Messages. Range = 70..79.
this.toStreamerMessages.add('MouseEnter', {
this.toStreamerMessages.set('MouseEnter', {
id: 70,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('MouseLeave', {
this.toStreamerMessages.set('MouseLeave', {
id: 71,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('MouseDown', {
this.toStreamerMessages.set('MouseDown', {
id: 72,
byteLength: 5,
// button x y
structure: ['uint8', 'uint16', 'uint16']
});
this.toStreamerMessages.add('MouseUp', {
this.toStreamerMessages.set('MouseUp', {
id: 73,
byteLength: 5,
// button x y
structure: ['uint8', 'uint16', 'uint16']
});
this.toStreamerMessages.add('MouseMove', {
this.toStreamerMessages.set('MouseMove', {
id: 74,
byteLength: 8,
// x y deltaX deltaY
structure: ['uint16', 'uint16', 'int16', 'int16']
});
this.toStreamerMessages.add('MouseWheel', {
this.toStreamerMessages.set('MouseWheel', {
id: 75,
byteLength: 6,
// delta x y
structure: ['int16', 'uint16', 'uint16']
});
this.toStreamerMessages.add('MouseDouble', {
this.toStreamerMessages.set('MouseDouble', {
id: 76,
byteLength: 5,
// button x y
structure: ['uint8', 'uint16', 'uint16']
});
// Touch Input Messages. Range = 80..89.
this.toStreamerMessages.add('TouchStart', {
this.toStreamerMessages.set('TouchStart', {
id: 80,
byteLength: 8,
// numtouches(1) x y idx force valid
structure: ['uint8', 'uint16', 'uint16', 'uint8', 'uint8', 'uint8']
});
this.toStreamerMessages.add('TouchEnd', {
this.toStreamerMessages.set('TouchEnd', {
id: 81,
byteLength: 8,
// numtouches(1) x y idx force valid
structure: ['uint8', 'uint16', 'uint16', 'uint8', 'uint8', 'uint8']
});
this.toStreamerMessages.add('TouchMove', {
this.toStreamerMessages.set('TouchMove', {
id: 82,
byteLength: 8,
// numtouches(1) x y idx force valid
structure: ['uint8', 'uint16', 'uint16', 'uint8', 'uint8', 'uint8']
});
// Gamepad Input Messages. Range = 90..99
this.toStreamerMessages.add('GamepadConnected', {
this.toStreamerMessages.set('GamepadConnected', {
id: 93,
byteLength: 0,
structure: []
});
this.toStreamerMessages.add('GamepadButtonPressed', {
this.toStreamerMessages.set('GamepadButtonPressed', {
id: 90,
byteLength: 3,
// ctrlerId button isRepeat
// ctrlerId button isRepeat
structure: ['uint8', 'uint8', 'uint8']
});
this.toStreamerMessages.add('GamepadButtonReleased', {
this.toStreamerMessages.set('GamepadButtonReleased', {
id: 91,
byteLength: 3,
// ctrlerId button isRepeat(0)
// ctrlerId button isRepeat(0)
structure: ['uint8', 'uint8', 'uint8']
});
this.toStreamerMessages.add('GamepadAnalog', {
this.toStreamerMessages.set('GamepadAnalog', {
id: 92,
byteLength: 10,
// ctrlerId button analogValue
// ctrlerId button analogValue
structure: ['uint8', 'uint8', 'double']
});
this.toStreamerMessages.add('GamepadDisconnected', {
this.toStreamerMessages.set('GamepadDisconnected', {
id: 94,
byteLength: 1,
// ctrlerId
structure: ['uint8']
});
this.fromStreamerMessages.add('QualityControlOwnership', 0);
this.fromStreamerMessages.add('Response', 1);
this.fromStreamerMessages.add('Command', 2);
this.fromStreamerMessages.add('FreezeFrame', 3);
this.fromStreamerMessages.add('UnfreezeFrame', 4);
this.fromStreamerMessages.add('VideoEncoderAvgQP', 5);
this.fromStreamerMessages.add('LatencyTest', 6);
this.fromStreamerMessages.add('InitialSettings', 7);
this.fromStreamerMessages.add('FileExtension', 8);
this.fromStreamerMessages.add('FileMimeType', 9);
this.fromStreamerMessages.add('FileContents', 10);
this.fromStreamerMessages.add('TestEcho', 11);
this.fromStreamerMessages.add('InputControlOwnership', 12);
this.fromStreamerMessages.add('GamepadResponse', 13);
this.fromStreamerMessages.add('Protocol', 255);
this.fromStreamerMessages.set(0, 'QualityControlOwnership');
this.fromStreamerMessages.set(1, 'Response');
this.fromStreamerMessages.set(2, 'Command');
this.fromStreamerMessages.set(3, 'FreezeFrame');
this.fromStreamerMessages.set(4, 'UnfreezeFrame');
this.fromStreamerMessages.set(5, 'VideoEncoderAvgQP');
this.fromStreamerMessages.set(6, 'LatencyTest');
this.fromStreamerMessages.set(7, 'InitialSettings');
this.fromStreamerMessages.set(8, 'FileExtension');
this.fromStreamerMessages.set(9, 'FileMimeType');
this.fromStreamerMessages.set(10, 'FileContents');
this.fromStreamerMessages.set(11, 'TestEcho');
this.fromStreamerMessages.set(12, 'InputControlOwnership');
this.fromStreamerMessages.set(13, 'GamepadResponse');
this.fromStreamerMessages.set(14, 'DataChannelLatencyTest');
this.fromStreamerMessages.set(255, 'Protocol');
}
/**

View File

@ -1,52 +0,0 @@
// Copyright Epic Games, Inc. All Rights Reserved.
export class TwoWayMap<KeyType, ValueType> {
map: Map<KeyType, ValueType>;
reverseMap: Map<ValueType, KeyType>;
/**
* @param map - an optional map of parameters
*/
constructor() {
this.map = new Map();
this.reverseMap = new Map();
}
/**
* Get the value from the map by key
* @param key - the key we are searching by
* @returns - the value associated with the key
*/
getFromKey(key: KeyType) {
return this.map.get(key);
}
/**
* Get the reverse key from the map by searching by value
* @param value - the key we are searching by
* @returns - they key associated with the value
*/
getFromValue(value: ValueType) {
return this.reverseMap.get(value);
}
/**
* Add a key and value to both the map and reverse map
* @param key - the indexing key
* @param value - the value associated with the key
*/
add(key: KeyType, value: ValueType) {
this.map.set(key, value);
this.reverseMap.set(value, key);
}
/**
* Remove a key and value from both the map and reverse map
* @param key - the indexing key
* @param value - the value associated with the key
*/
remove(key: KeyType, value: ValueType) {
this.map.delete(key);
this.reverseMap.delete(value);
}
}

View File

@ -12,6 +12,10 @@ import { SettingFlag } from '../Config/SettingFlag';
import { SettingNumber } from '../Config/SettingNumber';
import { SettingText } from '../Config/SettingText';
import { SettingOption } from '../Config/SettingOption';
import {
DataChannelLatencyTestResponse,
DataChannelLatencyTestResult
} from "../DataChannel/DataChannelLatencyTestResults";
/**
* An event that is emitted when AFK disconnect is about to happen.
@ -140,7 +144,7 @@ export class WebRtcDisconnectedEvent extends Event {
/** Message describing the disconnect reason */
eventString: string;
/** true if the user is able to reconnect, false if disconnected because of unrecoverable reasons like not able to connect to the signaling server */
showActionOrErrorOnDisconnect: boolean;
allowClickToReconnect: boolean;
};
constructor(data: WebRtcDisconnectedEvent['data']) {
super('webRtcDisconnected');
@ -343,7 +347,9 @@ export class StreamerListMessageEvent extends Event {
/** Streamer list message containing an array of streamer ids */
messageStreamerList: MessageStreamerList;
/** Auto-selected streamer from the list, or null if unable to auto-select and user should be prompted to select */
autoSelectedStreamerId: string | null;
autoSelectedStreamerId: string;
/** Wanted streamer id from various configurations. */
wantedStreamerId: string;
};
constructor(data: StreamerListMessageEvent['data']) {
super('streamerListMessage');
@ -351,6 +357,21 @@ export class StreamerListMessageEvent extends Event {
}
}
/**
* An event that is emitted when a subscribed to streamer's id changes.
*/
export class StreamerIDChangedMessageEvent extends Event {
readonly type: 'streamerIDChangedMessage';
readonly data: {
/** The new ID of the streamer. */
newID: string;
};
constructor(data: StreamerIDChangedMessageEvent['data']) {
super('StreamerIDChangedMessage');
this.data = data;
}
}
/**
* An event that is emitted when receiving latency test results.
*/
@ -366,6 +387,37 @@ export class LatencyTestResultEvent extends Event {
}
}
/**
* An event that is emitted when receiving data channel latency test response from server.
* This event is handled by DataChannelLatencyTestController
*/
export class DataChannelLatencyTestResponseEvent extends Event {
readonly type: 'dataChannelLatencyTestResponse';
readonly data: {
/** Latency test result object */
response: DataChannelLatencyTestResponse
};
constructor(data: DataChannelLatencyTestResponseEvent['data']) {
super('dataChannelLatencyTestResponse');
this.data = data;
}
}
/**
* An event that is emitted when data channel latency test results are ready.
*/
export class DataChannelLatencyTestResultEvent extends Event {
readonly type: 'dataChannelLatencyTestResult';
readonly data: {
/** Latency test result object */
result: DataChannelLatencyTestResult
};
constructor(data: DataChannelLatencyTestResultEvent['data']) {
super('dataChannelLatencyTestResult');
this.data = data;
}
}
/**
* An event that is emitted when receiving initial settings from UE.
*/
@ -470,6 +522,31 @@ export class XrFrameEvent extends Event {
}
}
/**
* An event that is emitted when receiving a player count from the signalling server
*/
export class PlayerCountEvent extends Event {
readonly type: 'playerCount';
readonly data: {
/** count object */
count: number
};
constructor(data: PlayerCountEvent['data']) {
super('playerCount');
this.data = data;
}
}
/**
* An event that is emitted when the webRTC connections is relayed over TCP.
*/
export class WebRtcTCPRelayDetectedEvent extends Event {
readonly type: 'webRtcTCPRelayDetected';
constructor() {
super('webRtcTCPRelayDetected');
}
}
export type PixelStreamingEvent =
| AfkWarningActivateEvent
| AfkWarningUpdateEvent
@ -497,12 +574,17 @@ export type PixelStreamingEvent =
| HideFreezeFrameEvent
| StatsReceivedEvent
| StreamerListMessageEvent
| StreamerIDChangedMessageEvent
| LatencyTestResultEvent
| DataChannelLatencyTestResponseEvent
| DataChannelLatencyTestResultEvent
| InitialSettingsEvent
| SettingsChangedEvent
| XrSessionStartedEvent
| XrSessionEndedEvent
| XrFrameEvent;
| XrFrameEvent
| PlayerCountEvent
| WebRtcTCPRelayDetectedEvent;
export class EventEmitter extends EventTarget {
/**

View File

@ -0,0 +1,41 @@
export class RTCUtils {
static isVideoTransciever(transceiver : RTCRtpTransceiver | undefined) : boolean {
return this.canTransceiverReceiveVideo(transceiver) || this.canTransceiverSendVideo(transceiver);
}
static canTransceiverReceiveVideo(transceiver : RTCRtpTransceiver | undefined) : boolean {
return !!transceiver &&
(transceiver.direction === 'sendrecv' || transceiver.direction === 'recvonly') &&
transceiver.receiver &&
transceiver.receiver.track &&
transceiver.receiver.track.kind === 'video';
}
static canTransceiverSendVideo(transceiver : RTCRtpTransceiver | undefined) : boolean {
return !!transceiver &&
(transceiver.direction === 'sendrecv' || transceiver.direction === 'sendonly') &&
transceiver.sender &&
transceiver.sender.track &&
transceiver.sender.track.kind === 'video';
}
static isAudioTransciever(transceiver : RTCRtpTransceiver | undefined) : boolean {
return this.canTransceiverReceiveAudio(transceiver) || this.canTransceiverSendAudio(transceiver);
}
static canTransceiverReceiveAudio(transceiver : RTCRtpTransceiver | undefined) : boolean {
return !!transceiver &&
(transceiver.direction === 'sendrecv' || transceiver.direction === 'recvonly') &&
transceiver.receiver &&
transceiver.receiver.track &&
transceiver.receiver.track.kind === 'audio';
}
static canTransceiverSendAudio(transceiver : RTCRtpTransceiver | undefined) : boolean {
return !!transceiver &&
(transceiver.direction === 'sendrecv' || transceiver.direction === 'sendonly') &&
transceiver.sender &&
transceiver.sender.track &&
transceiver.sender.track.kind === 'audio';
}
}

View File

@ -18,6 +18,7 @@ export class StreamController {
constructor(videoElementProvider: VideoPlayer) {
this.videoElementProvider = videoElementProvider;
this.audioElement = document.createElement('Audio') as HTMLAudioElement;
this.videoElementProvider.setAudioElement(this.audioElement);
}
/**

View File

@ -18,6 +18,7 @@ declare global {
export class VideoPlayer {
private config: Config;
private videoElement: HTMLVideoElement;
private audioElement?: HTMLAudioElement;
private orientationChangeTimeout: number;
private lastTimeResized = new Date().getTime();
@ -52,8 +53,11 @@ export class VideoPlayer {
);
};
// set play for video
// set play for video (and audio)
this.videoElement.onclick = () => {
if (this.audioElement != undefined && this.audioElement.paused) {
this.audioElement.play();
}
if (this.videoElement.paused) {
this.videoElement.play();
}
@ -70,6 +74,10 @@ export class VideoPlayer {
);
}
public setAudioElement(audioElement: HTMLAudioElement) : void {
this.audioElement = audioElement;
}
/**
* Sets up the video element with any application config and plays the video element.
* @returns A promise for if playing the video was successful or not.

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@
export enum MessageRecvTypes {
CONFIG = 'config',
STREAMER_LIST = 'streamerList',
STREAMER_ID_CHANGED = 'streamerIDChanged',
PLAYER_COUNT = 'playerCount',
OFFER = 'offer',
ANSWER = 'answer',
@ -42,6 +43,13 @@ export class MessageStreamerList extends MessageRecv {
ids: string[];
}
/**
* Streamer ID Changed Message Wrapper
*/
export class MessageStreamerIDChanged extends MessageRecv {
newID: string;
}
/**
* Player Count Message wrapper
*/

View File

@ -24,6 +24,20 @@ export class MessageSend implements Send {
type: string;
peerConnectionOptions: object;
/**
* A filter for controlling what parameters to actually send.
* Good for excluding default values or hidden internals.
* Example for including everything but zero bitrate fields...
* sendFilter(key: string, value: any) {
* if ((key == "minBitrate" || key == "maxBitrate") && value <= 0) return undefined;
* return value;
* }
* Return undefined to exclude the property completely.
*/
sendFilter(key: string, value: any) {
return value;
}
/**
* Turns the wrapper into a JSON String
* @returns - JSON String of the Message to send
@ -31,10 +45,10 @@ export class MessageSend implements Send {
payload() {
Logger.Log(
Logger.GetStackTrace(),
'Sending => \n' + JSON.stringify(this, undefined, 4),
'Sending => \n' + JSON.stringify(this, this.sendFilter, 4),
6
);
return JSON.stringify(this);
return JSON.stringify(this, this.sendFilter);
}
}
@ -83,24 +97,45 @@ export class MessagePong extends MessageSend {
}
}
export type ExtraOfferParameters = {
minBitrateBps: number;
maxBitrateBps: number;
}
/**
* Web RTC Offer message wrapper
*/
export class MessageWebRTCOffer extends MessageSend {
sdp: string;
minBitrate: number;
maxBitrate: number;
/**
* @param offer - Generated Web RTC Offer
*/
constructor(offer?: RTCSessionDescriptionInit) {
constructor(offer: RTCSessionDescriptionInit, extraParams: ExtraOfferParameters) {
super();
this.type = MessageSendTypes.OFFER;
this.minBitrate = 0;
this.maxBitrate = 0;
if (offer) {
this.type = offer.type as MessageSendTypes;
this.sdp = offer.sdp;
this.minBitrate = extraParams.minBitrateBps;
this.maxBitrate = extraParams.maxBitrateBps;
}
}
sendFilter(key: string, value: any) {
if ((key == "minBitrate" || key == "maxBitrate") && value <= 0) return undefined;
return value;
}
}
export type ExtraAnswerParameters = {
minBitrateBps: number;
maxBitrateBps: number;
}
/**
@ -108,19 +143,30 @@ export class MessageWebRTCOffer extends MessageSend {
*/
export class MessageWebRTCAnswer extends MessageSend {
sdp: string;
minBitrate: number;
maxBitrate: number;
/**
* @param answer - Generated Web RTC Offer
*/
constructor(answer?: RTCSessionDescriptionInit) {
constructor(answer: RTCSessionDescriptionInit, extraParams: ExtraAnswerParameters) {
super();
this.type = MessageSendTypes.ANSWER;
this.minBitrate = 0;
this.maxBitrate = 0;
if (answer) {
this.type = answer.type as MessageSendTypes;
this.sdp = answer.sdp;
this.minBitrate = extraParams.minBitrateBps;
this.maxBitrate = extraParams.maxBitrateBps;
}
}
sendFilter(key: string, value: any) {
if ((key == "minBitrate" || key == "maxBitrate") && value <= 0) return undefined;
return value;
}
}
/**

View File

@ -6,6 +6,7 @@ import {
MessageRecvTypes,
MessageConfig,
MessageStreamerList,
MessageStreamerIDChanged,
MessagePlayerCount,
MessageAnswer,
MessageOffer,
@ -92,6 +93,21 @@ export class SignallingProtocol {
}
);
// STREAMER_ID_CHANGED
websocketController.signallingProtocol.addMessageHandler(
MessageRecvTypes.STREAMER_ID_CHANGED,
(idPayload: string) => {
Logger.Log(
Logger.GetStackTrace(),
MessageRecvTypes.STREAMER_ID_CHANGED,
6
);
const streamerIdMessage: MessageStreamerIDChanged =
JSON.parse(idPayload);
websocketController.onStreamerIDChanged(streamerIdMessage);
}
);
// PLAYER_COUNT
websocketController.signallingProtocol.addMessageHandler(
MessageRecvTypes.PLAYER_COUNT,
@ -108,6 +124,7 @@ export class SignallingProtocol {
'Player Count: ' + playerCount.count,
6
);
websocketController.onPlayerCount(playerCount)
}
);

View File

@ -134,7 +134,6 @@ export class WebSocketController {
* @param event - Close Event
*/
handleOnClose(event: CloseEvent) {
this.onWebSocketOncloseOverlayMessage(event);
Logger.Log(
Logger.GetStackTrace(),
'Disconnected to the signalling server via WebSocket: ' +
@ -142,7 +141,7 @@ export class WebSocketController {
' - ' +
event.reason
);
this.onClose.dispatchEvent(new Event('close'));
this.onClose.dispatchEvent(new CustomEvent('close', { 'detail': event }));
}
requestStreamerList() {
@ -160,13 +159,13 @@ export class WebSocketController {
this.webSocket.send(payload.payload());
}
sendWebRtcOffer(offer: RTCSessionDescriptionInit) {
const payload = new MessageSend.MessageWebRTCOffer(offer);
sendWebRtcOffer(offer: RTCSessionDescriptionInit, extraParams: MessageSend.ExtraOfferParameters) {
const payload = new MessageSend.MessageWebRTCOffer(offer, extraParams);
this.webSocket.send(payload.payload());
}
sendWebRtcAnswer(answer: RTCSessionDescriptionInit) {
const payload = new MessageSend.MessageWebRTCAnswer(answer);
sendWebRtcAnswer(answer: RTCSessionDescriptionInit, extraParams: MessageSend.ExtraAnswerParameters) {
const payload = new MessageSend.MessageWebRTCAnswer(answer, extraParams);
this.webSocket.send(payload.payload());
}
@ -204,10 +203,6 @@ export class WebSocketController {
this.webSocket?.close();
}
/** Event used for Displaying websocket closed messages */
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onWebSocketOncloseOverlayMessage(event: CloseEvent) {}
/**
* The Message Contains the payload of the peer connection options used for the RTC Peer hand shake
* @param messageConfig - Config Message received from he signaling server
@ -216,12 +211,19 @@ export class WebSocketController {
onConfig(messageConfig: MessageReceive.MessageConfig) {}
/**
* The Message Contains the payload of the peer connection options used for the RTC Peer hand shake
* @param messageConfig - Config Message received from he signaling server
* The Message contains all the ids of streamers available on the server.
* @param messageStreamerList - The message with the list of the available streamer ids.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onStreamerList(messageStreamerList: MessageReceive.MessageStreamerList) {}
/**
* The Message contains the new id of a subscribed to streamer.
* @param message - Message conaining the new id of the streamer.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onStreamerIDChanged(message: MessageReceive.MessageStreamerIDChanged) {}
/**
* @param iceCandidate - Ice Candidate sent from the Signaling server server's RTC hand shake
*/
@ -247,7 +249,12 @@ export class WebSocketController {
* @param messageDataChannels - The data channels details
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onWebRtcPeerDataChannels(
messageDataChannels: MessageReceive.MessagePeerDataChannels
) {}
onWebRtcPeerDataChannels(messageDataChannels: MessageReceive.MessagePeerDataChannels) {}
/**
* Event is fired when the websocket receives the an updated player count from cirrus
* @param MessagePlayerCount - The new player count
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
onPlayerCount(playerCount: MessageReceive.MessagePlayerCount) {}
}

View File

@ -33,6 +33,7 @@ export {
export { AggregatedStats } from './PeerConnectionController/AggregatedStats';
export { Logger } from './Logger/Logger';
export { UnquantizedDenormalizedUnsignedCoord as UnquantizedAndDenormalizeUnsigned } from './Util/CoordinateConverter';
export { MessageDirection } from './UeInstanceMessage/StreamMessageController';
export { MessageSend } from './WebSockets/MessageSend';
export { MessageRecv, MessageStreamerList } from './WebSockets/MessageReceive';
export { WebSocketController } from './WebSockets/WebSocketController';

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.3",
"version": "0.0.1",
"description": "Reference frontend UI library for Unreal Engine 5.3 Pixel Streaming - gives the stock look and feel.",
"name": "@epicgames-ps/lib-pixelstreamingfrontend-ui-ue5.4",
"version": "0.0.3",
"description": "Reference frontend UI library for Unreal Engine 5.4 Pixel Streaming - gives the stock look and feel.",
"main": "dist/lib-pixelstreamingfrontend-ui.js",
"module": "dist/lib-pixelstreamingfrontend-ui.esm.js",
"types": "types/pixelstreamingfrontend-ui.d.ts",
@ -16,7 +16,7 @@
"spellcheck": "cspell \"{README.md,.github/*.md,src/**/*.ts}\""
},
"devDependencies": {
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^0.0.1",
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.4": "^0.0.3",
"@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0",
"cspell": "^4.1.0",
@ -34,7 +34,7 @@
"jss-plugin-global": "^10.9.2"
},
"peerDependencies": {
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.3": "^0.0.1"
"@epicgames-ps/lib-pixelstreamingfrontend-ue5.4": "^0.0.3"
},
"repository": {
"type": "git",

View File

@ -8,7 +8,7 @@ import {
LatencyTestResults,
InitialSettings,
MessageStreamerList
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { OverlayBase } from '../Overlay/BaseOverlay';
import { ActionOverlay } from '../Overlay/ActionOverlay';
import { TextOverlay } from '../Overlay/TextOverlay';
@ -31,6 +31,9 @@ import {
UIElementConfig
} from '../UI/UIConfigurationTypes'
import { FullScreenIconBase, FullScreenIconExternal } from '../UI/FullscreenIcon';
import {
DataChannelLatencyTestResult
} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.4/types/DataChannel/DataChannelLatencyTestResults";
/**
@ -195,7 +198,7 @@ export class Application {
// Or use the one created by the Controls initializer earlier
: controls.fullscreenIcon;
if (fullScreenButton) {
fullScreenButton.fullscreenElement = /iPhone|iPod/.test(navigator.userAgent) ? this.stream.videoElementParent.getElementsByTagName("video")[0] : this.rootElement;
fullScreenButton.fullscreenElement = /iPad|iPhone|iPod/.test(navigator.userAgent) ? this.stream.videoElementParent.getElementsByTagName("video")[0] : this.rootElement;
}
// Add settings button to controls
@ -321,8 +324,8 @@ export class Application {
);
this.stream.addEventListener(
'webRtcDisconnected',
({ data: { eventString, showActionOrErrorOnDisconnect } }) =>
this.onDisconnect(eventString, showActionOrErrorOnDisconnect)
({ data: { eventString, allowClickToReconnect } }) =>
this.onDisconnect(eventString, allowClickToReconnect)
);
this.stream.addEventListener('videoInitialized', () =>
this.onVideoInitialized()
@ -356,15 +359,33 @@ export class Application {
({ data: { latencyTimings } }) =>
this.onLatencyTestResults(latencyTimings)
);
this.stream.addEventListener(
'dataChannelLatencyTestResult',
({data: { result } }) =>
this.onDataChannelLatencyTestResults(result)
)
this.stream.addEventListener(
'streamerListMessage',
({ data: { messageStreamerList, autoSelectedStreamerId } }) =>
this.handleStreamerListMessage(messageStreamerList, autoSelectedStreamerId)
({ data: { messageStreamerList, autoSelectedStreamerId, wantedStreamerId } }) =>
this.handleStreamerListMessage(messageStreamerList, autoSelectedStreamerId, wantedStreamerId)
);
this.stream.addEventListener(
'settingsChanged',
(event) => this.configUI.onSettingsChanged(event)
);
this.stream.addEventListener(
'playerCount',
({ data: { count }}) =>
this.onPlayerCount(count)
);
this.stream.addEventListener(
'webRtcTCPRelayDetected',
({}) =>
Logger.Warning(
Logger.GetStackTrace(),
`Stream quailty degraded due to network enviroment, stream is relayed over TCP.`
)
);
}
/**
@ -474,7 +495,7 @@ export class Application {
* Shows or hides the settings panel if clicked
*/
settingsClicked() {
this.statsPanel.hide();
this.statsPanel?.hide();
this.settingsPanel.toggleVisibility();
}
@ -482,7 +503,7 @@ export class Application {
* Shows or hides the stats panel if clicked
*/
statsClicked() {
this.settingsPanel.hide();
this.settingsPanel?.hide();
this.statsPanel.toggleVisibility();
}
@ -560,19 +581,17 @@ export class Application {
/**
* Event fired when the video is disconnected - displays the error overlay and resets the buttons stream tools upon disconnect
* @param eventString - the event text that will be shown in the overlay
* @param allowClickToReconnect - true if we want to allow the user to click to reconnect. Otherwise it's just a message.
*/
onDisconnect(eventString: string, showActionOrErrorOnDisconnect: boolean) {
if (showActionOrErrorOnDisconnect == false) {
this.showErrorOverlay(`Disconnected: ${eventString}`);
onDisconnect(eventString: string, allowClickToReconnect: boolean) {
const overlayMessage = 'Disconnected' + (eventString ? `: ${eventString}` : '');
if (allowClickToReconnect) {
this.showDisconnectOverlay(`${overlayMessage} Click To Restart.`);
} else {
this.showDisconnectOverlay(
`Disconnected: ${eventString} <div class="clickableState">Click To Restart</div>`
);
this.showErrorOverlay(overlayMessage);
}
// disable starting a latency check
this.statsPanel.latencyTest.latencyTestButton.onclick = () => {
// do nothing
};
// disable starting a latency checks
this.statsPanel?.onDisconnect();
}
/**
@ -619,11 +638,7 @@ export class Application {
if (!this.stream.config.isFlagEnabled(Flags.AutoPlayVideo)) {
this.showPlayOverlay();
}
// starting a latency check
this.statsPanel.latencyTest.latencyTestButton.onclick = () => {
this.stream.requestLatencyTest();
};
this.statsPanel?.onVideoInitialized(this.stream);
}
/**
@ -639,39 +654,62 @@ export class Application {
onInitialSettings(settings: InitialSettings) {
if (settings.PixelStreamingSettings) {
const disableLatencyTest =
settings.PixelStreamingSettings.DisableLatencyTest;
if (disableLatencyTest) {
this.statsPanel.latencyTest.latencyTestButton.disabled = true;
this.statsPanel.latencyTest.latencyTestButton.title =
'Disabled by -PixelStreamingDisableLatencyTester=true';
Logger.Info(
Logger.GetStackTrace(),
'-PixelStreamingDisableLatencyTester=true, requesting latency report from the the browser to UE is disabled.'
);
}
this.statsPanel?.configure(settings.PixelStreamingSettings);
}
}
onStatsReceived(aggregatedStats: AggregatedStats) {
// Grab all stats we can off the aggregated stats
this.statsPanel.handleStats(aggregatedStats);
this.statsPanel?.handleStats(aggregatedStats);
}
onLatencyTestResults(latencyTimings: LatencyTestResults) {
this.statsPanel.latencyTest.handleTestResult(latencyTimings);
this.statsPanel?.latencyTest.handleTestResult(latencyTimings);
}
handleStreamerListMessage(messageStreamingList: MessageStreamerList, autoSelectedStreamerId: string | null) {
if (autoSelectedStreamerId === null) {
if(messageStreamingList.ids.length === 0) {
this.showDisconnectOverlay(
'No streamers connected. <div class="clickableState">Click To Restart</div>'
);
onDataChannelLatencyTestResults(result: DataChannelLatencyTestResult) {
this.statsPanel?.dataChannelLatencyTest.handleTestResult(result);
}
onPlayerCount(playerCount: number) {
this.statsPanel?.handlePlayerCount(playerCount);
}
handleStreamerListMessage(messageStreamingList: MessageStreamerList, autoSelectedStreamerId: string, wantedStreamerId: string) {
const waitForStreamer = this.stream.config.isFlagEnabled(Flags.WaitForStreamer);
const isReconnecting = this.stream.isReconnecting();
let message: string = null;
let allowRestart: boolean = true;
if (!autoSelectedStreamerId) {
if (waitForStreamer && wantedStreamerId) {
if (isReconnecting) {
message = `Waiting for ${wantedStreamerId} to become available.`;
allowRestart = false;
} else {
message = `Gave up waiting for ${wantedStreamerId} to become available. Click to try again`;
if (messageStreamingList.ids.length > 0) {
message += ` or select a streamer from the settings menu.`;
}
allowRestart = true;
}
} else if (messageStreamingList.ids.length == 0) {
if (isReconnecting) {
message = `Waiting for a streamer to become available.`;
allowRestart = false;
} else {
message = `No streamers available. Click to try again.`;
allowRestart = true;
}
} else {
this.showTextOverlay(
'Multiple streamers detected. Use the dropdown in the settings menu to select the streamer'
);
message = `Multiple streamers available. Select one from the settings menu.`;
allowRestart = false;
}
if (allowRestart) {
this.showDisconnectOverlay(message);
} else {
this.showTextOverlay(message);
}
}
}

View File

@ -17,7 +17,7 @@ import {
SettingOption,
Logger,
SettingBase
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { SettingUIFlag } from './SettingUIFlag';
import { SettingUINumber } from './SettingUINumber';
import { SettingUIText } from './SettingUIText';
@ -174,10 +174,6 @@ export class ConfigUI {
psSettingsSection,
this.flagsUi.get(Flags.StartVideoMuted)
);
this.addSettingFlag(
psSettingsSection,
this.flagsUi.get(Flags.PreferSFU)
);
this.addSettingFlag(
psSettingsSection,
this.flagsUi.get(Flags.IsQualityController)
@ -198,6 +194,10 @@ export class ConfigUI {
psSettingsSection,
this.flagsUi.get(Flags.AFKDetection)
);
this.addSettingFlag(
psSettingsSection,
this.flagsUi.get(Flags.WaitForStreamer)
);
this.addSettingNumeric(
psSettingsSection,
this.numericParametersUi.get(NumericParameters.AFKTimeoutSecs)
@ -206,6 +206,10 @@ export class ConfigUI {
psSettingsSection,
this.numericParametersUi.get(NumericParameters.MaxReconnectAttempts)
);
this.addSettingNumeric(
psSettingsSection,
this.numericParametersUi.get(NumericParameters.StreamerAutoJoinInterval)
);
/* Setup all view/ui related settings under this section */
const viewSettingsSection = this.buildSectionWithHeading(

View File

@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { SettingBase } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { SettingBase } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
/**
* Base class for a setting that has a text label, an arbitrary setting value it stores, an a HTML element that represents this setting.

View File

@ -3,7 +3,7 @@
import type {
FlagsIds,
SettingFlag
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { SettingUIBase } from './SettingUIBase';
export class SettingUIFlag<

View File

@ -3,8 +3,8 @@
import type {
NumericParametersIds,
SettingNumber
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { SettingUIBase } from './SettingUIBase';
/**
@ -77,7 +77,7 @@ export class SettingUINumber<
this.spinner.onchange = (event: Event) => {
const inputElem = event.target as HTMLInputElement;
const parsedValue = Number.parseInt(inputElem.value);
const parsedValue = Number.parseFloat(inputElem.value);
if (Number.isNaN(parsedValue)) {
Logger.Warning(

View File

@ -3,7 +3,7 @@
import type {
OptionParametersIds,
SettingOption
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { SettingUIBase } from './SettingUIBase';
export class SettingUIOption<

View File

@ -3,7 +3,7 @@
import type {
SettingText,
TextParametersIds
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { SettingUIBase } from './SettingUIBase';
export class SettingUIText<

View File

@ -1,6 +1,6 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { OverlayBase } from './BaseOverlay';

View File

@ -526,14 +526,16 @@ export class PixelStreamingApplicationStyle {
customStyles?: Partial<Styles>;
lightModePalette?: ColorPalette;
darkModePalette?: ColorPalette;
jssInsertionPoint?: string | HTMLElement;
}) {
const { customStyles, lightModePalette, darkModePalette } =
const { customStyles, lightModePalette, darkModePalette, jssInsertionPoint } =
options ?? {};
// One time setup with default plugins and settings.
const jssOptions = {
// JSS has many interesting plugins we may wish to turn on
//plugins: [functions(), template(), global(), extend(), nested(), compose(), camelCase(), defaultUnit(options.defaultUnit), expand(), vendorPrefixer(), propsSort()]
plugins: [global(), camelCase()]
plugins: [global(), camelCase()],
insertionPoint: jssInsertionPoint
};
jss.setup(jssOptions);

View File

@ -4,7 +4,7 @@ import { FullScreenIcon } from './FullscreenIcon';
import { SettingsIcon } from './SettingsIcon';
import { StatsIcon } from './StatsIcon';
import { XRIcon } from './XRIcon';
import { WebXRController } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { WebXRController } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { UIElementConfig, UIElementCreationMode } from '../UI/UIConfigurationTypes'
/**

View File

@ -0,0 +1,129 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import {
DataChannelLatencyTestResult
} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.4/types/DataChannel/DataChannelLatencyTestResults";
/**
* DataChannel Latency test UI elements and results handling.
*/
export class DataChannelLatencyTest {
_rootElement: HTMLElement;
_latencyTestButton: HTMLInputElement;
_latencyTestResultsElement: HTMLElement;
/**
* Get the button containing the stats icon.
*/
public get rootElement(): HTMLElement {
if (!this._rootElement) {
this._rootElement = document.createElement('section');
this._rootElement.classList.add('settingsContainer');
// make heading
const heading = document.createElement('div');
heading.id = 'dataChannelLatencyTestHeader';
heading.classList.add('settings-text');
heading.classList.add('settingsHeader');
this._rootElement.appendChild(heading);
const headingText = document.createElement('div');
headingText.innerHTML = 'Data Channel Latency Test';
heading.appendChild(headingText);
heading.appendChild(this.latencyTestButton);
// make test results element
const resultsParentElem = document.createElement('div');
resultsParentElem.id = 'dataChannelLatencyTestContainer';
resultsParentElem.classList.add('d-none');
this._rootElement.appendChild(resultsParentElem);
resultsParentElem.appendChild(this.latencyTestResultsElement);
}
return this._rootElement;
}
public get latencyTestResultsElement(): HTMLElement {
if (!this._latencyTestResultsElement) {
this._latencyTestResultsElement = document.createElement('div');
this._latencyTestResultsElement.id = 'dataChannelLatencyStatsResults';
this._latencyTestResultsElement.classList.add('StatsResult');
}
return this._latencyTestResultsElement;
}
public get latencyTestButton(): HTMLInputElement {
if (!this._latencyTestButton) {
this._latencyTestButton = document.createElement('input');
this._latencyTestButton.type = 'button';
this._latencyTestButton.value = 'Run Test';
this._latencyTestButton.id = 'btn-start-data-channel-latency-test';
this._latencyTestButton.classList.add('streamTools-button');
this._latencyTestButton.classList.add('btn-flat');
}
return this._latencyTestButton;
}
/**
* Populate the UI based on the latency test's results.
* @param result The latency test results.
*/
public handleTestResult(result: DataChannelLatencyTestResult) {
Logger.Log(
Logger.GetStackTrace(),
result.toString(),
6
);
/**
* Check we have results, NaN would mean that UE version we talk to doesn't support our test
*/
if (isNaN(result.dataChannelRtt)) {
this.latencyTestResultsElement.innerHTML = '<div>Not supported</div>';
return;
}
let latencyStatsInnerHTML = '';
latencyStatsInnerHTML +=
'<div>Data channel RTT (ms): ' +
result.dataChannelRtt +
'</div>';
/**
* Separate path time discovery works only when UE and Player clocks have been synchronized.
*/
if (result.playerToStreamerTime >= 0 && result.streamerToPlayerTime >= 0) {
latencyStatsInnerHTML +=
'<div>Player to Streamer path (ms): ' + result.playerToStreamerTime + '</div>';
latencyStatsInnerHTML +=
'<div>Streamer to Player path (ms): ' +
result.streamerToPlayerTime +
'</div>';
}
this.latencyTestResultsElement.innerHTML = latencyStatsInnerHTML;
//setup button to download the detailed results
let downloadButton: HTMLInputElement = document.createElement('input');
downloadButton.type = 'button';
downloadButton.value = 'Download';
downloadButton.classList.add('streamTools-button');
downloadButton.classList.add('btn-flat');
downloadButton.onclick = () => {
let file = new Blob([result.exportLatencyAsCSV()], {type: 'text/plain'});
let a = document.createElement("a"),
url = URL.createObjectURL(file);
a.href = url;
a.download = "data_channel_latency_test_results.csv";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
this.latencyTestResultsElement.appendChild(downloadButton);
}
public handleTestStart() {
this.latencyTestResultsElement.innerHTML =
'<div>Test in progress</div>';
}
}

View File

@ -1,7 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { LatencyTestResults } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { LatencyTestResults } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
/**
* Latency test UI elements and results handling.

View File

@ -1,9 +1,11 @@
// Copyright Epic Games, Inc. All Rights Reserved.
import { LatencyTest } from './LatencyTest';
import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { AggregatedStats } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3';
import { CandidatePairStats, InitialSettings, Logger, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { AggregatedStats } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { MathUtils } from '../Util/MathUtils';
import {DataChannelLatencyTest} from "./DataChannelLatencyTest";
import {PixelStreamingSettings} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.4/types/DataChannel/InitialSettings";
/**
* A stat structure, an id, the stat string, and the element where it is rendered.
@ -26,12 +28,14 @@ export class StatsPanel {
_statsResult: HTMLElement;
latencyTest: LatencyTest;
dataChannelLatencyTest: DataChannelLatencyTest;
/* A map stats we are storing/rendering */
statsMap = new Map<string, Stat>();
constructor() {
this.latencyTest = new LatencyTest();
this.dataChannelLatencyTest = new DataChannelLatencyTest();
}
/**
@ -91,6 +95,7 @@ export class StatsPanel {
statistics.appendChild(this.statisticsContainer);
controlStats.appendChild(this.latencyTest.rootElement);
controlStats.appendChild(this.dataChannelLatencyTest.rootElement);
}
return this._statsContentElement;
}
@ -122,6 +127,48 @@ export class StatsPanel {
return this._statsCloseButton;
}
public onDisconnect(): void {
this.latencyTest.latencyTestButton.onclick = () => {
// do nothing
}
this.dataChannelLatencyTest.latencyTestButton.onclick = () => {
//do nothing
}
}
public onVideoInitialized(stream: PixelStreaming): void {
// starting a latency check
this.latencyTest.latencyTestButton.onclick = () => {
stream.requestLatencyTest();
};
this.dataChannelLatencyTest.latencyTestButton.onclick = () => {
let started = stream.requestDataChannelLatencyTest({
duration: 1000,
rps: 10,
requestSize: 200,
responseSize: 200
});
if (started) {
this.dataChannelLatencyTest.handleTestStart();
}
};
}
public configure(settings: PixelStreamingSettings): void {
if (settings.DisableLatencyTest) {
this.latencyTest.latencyTestButton.disabled = true;
this.latencyTest.latencyTestButton.title =
'Disabled by -PixelStreamingDisableLatencyTester=true';
this.dataChannelLatencyTest.latencyTestButton.disabled = true;
this.dataChannelLatencyTest.latencyTestButton.title =
'Disabled by -PixelStreamingDisableLatencyTester=true';
Logger.Info(
Logger.GetStackTrace(),
'-PixelStreamingDisableLatencyTester=true, requesting latency report from the the browser to UE is disabled.'
);
}
}
/**
* Show stats panel.
*/
@ -147,6 +194,14 @@ export class StatsPanel {
}
}
public handlePlayerCount(playerCount: number) {
this.addOrUpdateStat(
'PlayerCountStat',
'Players',
playerCount.toString()
);
}
/**
* Handle stats coming in from browser/UE
* @param stats the stats structure
@ -263,14 +318,17 @@ export class StatsPanel {
);
}
// Store the active candidate pair return a new Candidate pair stat if getActiveCandidate is null
let activeCandidatePair = stats.getActiveCandidatePair() != null ? stats.getActiveCandidatePair() : new CandidatePairStats();
// RTT
const netRTT =
Object.prototype.hasOwnProperty.call(
stats.candidatePair,
activeCandidatePair,
'currentRoundTripTime'
) && stats.isNumber(stats.candidatePair.currentRoundTripTime)
) && stats.isNumber(activeCandidatePair.currentRoundTripTime)
? numberFormat.format(
stats.candidatePair.currentRoundTripTime * 1000
activeCandidatePair.currentRoundTripTime * 1000
)
: "Can't calculate";
this.addOrUpdateStat('RTTStat', 'Net RTT (ms)', netRTT);

View File

@ -21,7 +21,7 @@ module.exports = {
extensions: ['.tsx', '.ts', '.js']
},
externals: {
'@epicgames-ps/lib-pixelstreamingfrontend-ue5.3': '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3',
'@epicgames-ps/lib-pixelstreamingfrontend-ue5.4': '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4',
jss: 'jss',
'jss-plugin-camel-case': 'jss-plugin-camel-case',
'jss-plugin-global': 'jss-plugin-global'
@ -32,7 +32,6 @@ module.exports = {
})
],
output: {
path: path.resolve(__dirname, 'dist'),
globalObject: 'this'
}
};
};

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