warpr 0.0.1 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/ng-package.json +6 -6
- package/package.json +12 -10
- package/src/lib/components/stream-host/stream-host.component.html +5 -5
- package/src/lib/components/stream-host/stream-host.component.scss +31 -31
- package/src/lib/components/stream-host/stream-host.component.spec.ts +23 -23
- package/src/lib/components/stream-host/stream-host.component.ts +170 -170
- package/src/lib/data/frames.ts +37 -37
- package/src/lib/data/signaling-messages.ts +37 -37
- package/src/lib/data/streaming-messages.ts +136 -136
- package/src/lib/infrastructure/array-stream.ts +109 -109
- package/src/lib/infrastructure/events.ts +61 -61
- package/src/lib/infrastructure/message-assembler.ts +60 -60
- package/src/lib/networking/messaging-client.ts +7 -7
- package/src/lib/networking/web-socket-client.ts +50 -50
- package/src/lib/services/messaging.service.spec.ts +16 -16
- package/src/lib/services/messaging.service.ts +31 -31
- package/src/lib/services/streaming.service.spec.ts +16 -16
- package/src/lib/services/streaming.service.ts +150 -150
- package/src/public-api.ts +15 -15
- package/tsconfig.lib.json +14 -14
- package/tsconfig.lib.prod.json +10 -10
- package/tsconfig.spec.json +14 -14
- package/warpr-1.0.9.tgz +0 -0
package/README.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# WarprLib
|
|
2
|
-
|
|
3
|
-
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
|
|
4
|
-
|
|
5
|
-
## Code scaffolding
|
|
6
|
-
|
|
7
|
-
Run `ng generate component component-name --project warpr-lib` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project warpr-lib`.
|
|
8
|
-
> Note: Don't forget to add `--project warpr-lib` or else it will be added to the default project in your `angular.json` file.
|
|
9
|
-
|
|
10
|
-
## Build
|
|
11
|
-
|
|
12
|
-
Run `ng build warpr-lib` to build the project. The build artifacts will be stored in the `dist/` directory.
|
|
13
|
-
|
|
14
|
-
## Publishing
|
|
15
|
-
|
|
16
|
-
After building your library with `ng build warpr-lib`, go to the dist folder `cd dist/warpr-lib` and run `npm publish`.
|
|
17
|
-
|
|
18
|
-
## Running unit tests
|
|
19
|
-
|
|
20
|
-
Run `ng test warpr-lib` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
|
21
|
-
|
|
22
|
-
## Further help
|
|
23
|
-
|
|
24
|
-
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
|
1
|
+
# WarprLib
|
|
2
|
+
|
|
3
|
+
This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.0.
|
|
4
|
+
|
|
5
|
+
## Code scaffolding
|
|
6
|
+
|
|
7
|
+
Run `ng generate component component-name --project warpr-lib` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project warpr-lib`.
|
|
8
|
+
> Note: Don't forget to add `--project warpr-lib` or else it will be added to the default project in your `angular.json` file.
|
|
9
|
+
|
|
10
|
+
## Build
|
|
11
|
+
|
|
12
|
+
Run `ng build warpr-lib` to build the project. The build artifacts will be stored in the `dist/` directory.
|
|
13
|
+
|
|
14
|
+
## Publishing
|
|
15
|
+
|
|
16
|
+
After building your library with `ng build warpr-lib`, go to the dist folder `cd dist/warpr-lib` and run `npm publish`.
|
|
17
|
+
|
|
18
|
+
## Running unit tests
|
|
19
|
+
|
|
20
|
+
Run `ng test warpr-lib` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
|
21
|
+
|
|
22
|
+
## Further help
|
|
23
|
+
|
|
24
|
+
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
package/ng-package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
|
3
|
-
"dest": "../../dist/warpr-lib",
|
|
4
|
-
"lib": {
|
|
5
|
-
"entryFile": "src/public-api.ts"
|
|
6
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
|
|
3
|
+
"dest": "../../dist/warpr-lib",
|
|
4
|
+
"lib": {
|
|
5
|
+
"entryFile": "src/public-api.ts"
|
|
6
|
+
}
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
2
|
+
"name": "warpr",
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"license": "MPL-2.0",
|
|
5
|
+
"description": "Warper is a WebRTC based ultra-low latency video streaming library.",
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"@angular/common": "^17.3.0",
|
|
8
|
+
"@angular/core": "^17.3.0"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"tslib": "^2.3.0"
|
|
12
|
+
},
|
|
13
|
+
"sideEffects": false
|
|
12
14
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<div #root class="root">
|
|
2
|
-
<canvas #renderTarget class="renderTarget"></canvas>
|
|
3
|
-
|
|
4
|
-
<div class="overlay" (click)="ToggleFullScreen()" *ngIf="IsFullScreenAvailable" title="Warpr">W</div>
|
|
5
|
-
</div>
|
|
1
|
+
<div #root class="root">
|
|
2
|
+
<canvas #renderTarget class="renderTarget"></canvas>
|
|
3
|
+
|
|
4
|
+
<div class="overlay" (click)="ToggleFullScreen()" *ngIf="IsFullScreenAvailable" title="Warpr">W</div>
|
|
5
|
+
</div>
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
.root {
|
|
2
|
-
background-color: black;
|
|
3
|
-
color: white;
|
|
4
|
-
display: flex;
|
|
5
|
-
position: relative;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.renderTarget {
|
|
9
|
-
width: 100%;
|
|
10
|
-
height: 100%;
|
|
11
|
-
object-fit: contain;
|
|
12
|
-
touch-action: none;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.overlay {
|
|
16
|
-
position: absolute;
|
|
17
|
-
bottom: 10px;
|
|
18
|
-
right: 10px;
|
|
19
|
-
background-color: rgba(0, 0, 0, 0.5);
|
|
20
|
-
color: rgba(255,255,255,0.75);
|
|
21
|
-
padding: 10px;
|
|
22
|
-
border-radius: 5px;
|
|
23
|
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
24
|
-
font-weight: bold;
|
|
25
|
-
cursor: pointer;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.overlay:hover {
|
|
29
|
-
background-color: rgba(0, 0, 0, 0.75);
|
|
30
|
-
color: white;
|
|
31
|
-
}
|
|
1
|
+
.root {
|
|
2
|
+
background-color: black;
|
|
3
|
+
color: white;
|
|
4
|
+
display: flex;
|
|
5
|
+
position: relative;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.renderTarget {
|
|
9
|
+
width: 100%;
|
|
10
|
+
height: 100%;
|
|
11
|
+
object-fit: contain;
|
|
12
|
+
touch-action: none;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.overlay {
|
|
16
|
+
position: absolute;
|
|
17
|
+
bottom: 10px;
|
|
18
|
+
right: 10px;
|
|
19
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
20
|
+
color: rgba(255,255,255,0.75);
|
|
21
|
+
padding: 10px;
|
|
22
|
+
border-radius: 5px;
|
|
23
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
24
|
+
font-weight: bold;
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.overlay:hover {
|
|
29
|
+
background-color: rgba(0, 0, 0, 0.75);
|
|
30
|
+
color: white;
|
|
31
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
|
|
3
|
-
import { StreamHostComponent } from './stream-host.component';
|
|
4
|
-
|
|
5
|
-
describe('StreamHostComponent', () => {
|
|
6
|
-
let component: StreamHostComponent;
|
|
7
|
-
let fixture: ComponentFixture<StreamHostComponent>;
|
|
8
|
-
|
|
9
|
-
beforeEach(async () => {
|
|
10
|
-
await TestBed.configureTestingModule({
|
|
11
|
-
declarations: [StreamHostComponent]
|
|
12
|
-
})
|
|
13
|
-
.compileComponents();
|
|
14
|
-
|
|
15
|
-
fixture = TestBed.createComponent(StreamHostComponent);
|
|
16
|
-
component = fixture.componentInstance;
|
|
17
|
-
fixture.detectChanges();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should create', () => {
|
|
21
|
-
expect(component).toBeTruthy();
|
|
22
|
-
});
|
|
23
|
-
});
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { StreamHostComponent } from './stream-host.component';
|
|
4
|
+
|
|
5
|
+
describe('StreamHostComponent', () => {
|
|
6
|
+
let component: StreamHostComponent;
|
|
7
|
+
let fixture: ComponentFixture<StreamHostComponent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
declarations: [StreamHostComponent]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(StreamHostComponent);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
import { Component, ElementRef, ViewChild } from '@angular/core';
|
|
2
|
-
import { StreamingService } from '../../services/streaming.service';
|
|
3
|
-
import { EncodedFrame, FrameType, Size } from '../../data/frames';
|
|
4
|
-
import { PointerInputMessage, PointerStates, ResizeSurfaceMessage } from '../../data/streaming-messages';
|
|
5
|
-
import { CommonModule } from '@angular/common';
|
|
6
|
-
|
|
7
|
-
@Component({
|
|
8
|
-
selector: 'warpr-stream-host',
|
|
9
|
-
standalone: true,
|
|
10
|
-
imports: [CommonModule],
|
|
11
|
-
templateUrl: './stream-host.component.html',
|
|
12
|
-
styleUrl: './stream-host.component.scss'
|
|
13
|
-
})
|
|
14
|
-
export class StreamHostComponent {
|
|
15
|
-
|
|
16
|
-
@ViewChild("root")
|
|
17
|
-
private _root?: ElementRef<HTMLDivElement>;
|
|
18
|
-
|
|
19
|
-
@ViewChild("renderTarget")
|
|
20
|
-
private _canvas?: ElementRef<HTMLCanvasElement>;
|
|
21
|
-
private _renderingContext: CanvasRenderingContext2D | null = null;
|
|
22
|
-
|
|
23
|
-
private _decoder: VideoDecoder;
|
|
24
|
-
private _framePending?: VideoFrame;
|
|
25
|
-
private _videoSize = Size.Empty;
|
|
26
|
-
private _displaySize = Size.Empty;
|
|
27
|
-
private _isDecoderInitialized = false;
|
|
28
|
-
private _pointerStates: PointerStates = {};
|
|
29
|
-
|
|
30
|
-
public get IsFullScreenAvailable(): boolean {
|
|
31
|
-
return document.fullscreenEnabled;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public constructor(
|
|
35
|
-
private _streamingService: StreamingService) {
|
|
36
|
-
this._streamingService.Connected.Subscribe((sender, eventArgs) => this.OnConnected());
|
|
37
|
-
this._streamingService.FrameReceived.Subscribe((sender, eventArgs) => this.OnFrameReceived(eventArgs));
|
|
38
|
-
|
|
39
|
-
this._decoder = new VideoDecoder({
|
|
40
|
-
output: (frame) => this.OnFrameDecoded(frame),
|
|
41
|
-
error: (error) => console.log(error)
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private async ngAfterViewInit() {
|
|
46
|
-
|
|
47
|
-
console.log("Initializing stream host...");
|
|
48
|
-
|
|
49
|
-
let canvas = this._canvas!.nativeElement;
|
|
50
|
-
this._renderingContext = canvas.getContext("2d")!;
|
|
51
|
-
canvas.onpointerdown = (event) => this.OnPointerEvent(event);
|
|
52
|
-
canvas.onpointermove = (event) => this.OnPointerEvent(event);
|
|
53
|
-
canvas.onpointerup = (event) => this.OnPointerEvent(event);
|
|
54
|
-
canvas.onwheel = (event) => this.OnWheelEvent(event);
|
|
55
|
-
canvas.oncontextmenu = (event) => event.preventDefault();
|
|
56
|
-
|
|
57
|
-
if (this._renderingContext) {
|
|
58
|
-
console.log("Stream host initialized.");
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
console.log("Stream host failed to initialize.");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
requestAnimationFrame(() => this.RenderFrame());
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
private OnPointerEvent(event: PointerEvent, wheelDelta = 0) {
|
|
68
|
-
//console.log(event);
|
|
69
|
-
event.preventDefault();
|
|
70
|
-
|
|
71
|
-
if (event.type == "pointerdown") this._canvas!.nativeElement.setPointerCapture(event.pointerId);
|
|
72
|
-
|
|
73
|
-
let message = new PointerInputMessage(event, this._pointerStates, this._canvas!.nativeElement, wheelDelta);
|
|
74
|
-
//console.log(message);
|
|
75
|
-
|
|
76
|
-
this._streamingService.SendMessage(message);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private OnWheelEvent(event: WheelEvent) {
|
|
80
|
-
//console.log(event);
|
|
81
|
-
event.preventDefault();
|
|
82
|
-
|
|
83
|
-
let pointerEvent: PointerEvent = new PointerEvent(event.type, event);
|
|
84
|
-
this.OnPointerEvent(pointerEvent, event.deltaY);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private OnConnected() {
|
|
88
|
-
this._displaySize = Size.Empty;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
private OnFrameReceived(frame: EncodedFrame) {
|
|
92
|
-
let chunk = new EncodedVideoChunk({
|
|
93
|
-
data: frame.Bytes,
|
|
94
|
-
type: frame.Type == FrameType.Key ? "key" : "delta",
|
|
95
|
-
timestamp: frame.Index
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
//Initialize decoder
|
|
99
|
-
let frameSize = new Size(frame.Width, frame.Height);
|
|
100
|
-
|
|
101
|
-
if (!Size.AreEqual(frameSize, this._videoSize)) {
|
|
102
|
-
console.log("Configuring decoder...");
|
|
103
|
-
this._decoder.configure({
|
|
104
|
-
codec: "hvc1.1.6.L93.B0",
|
|
105
|
-
codedWidth: frame.Width,
|
|
106
|
-
codedHeight: frame.Height,
|
|
107
|
-
hardwareAcceleration: "prefer-hardware",
|
|
108
|
-
optimizeForLatency: true
|
|
109
|
-
});
|
|
110
|
-
console.log("Decoder configured.");
|
|
111
|
-
console.log(`Resolution is ${frame.Width} x ${frame.Height}.`);
|
|
112
|
-
|
|
113
|
-
this._videoSize = frameSize;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
//Decode frame
|
|
117
|
-
if (!this._isDecoderInitialized && chunk.type != "key") return;
|
|
118
|
-
this._isDecoderInitialized = true;
|
|
119
|
-
|
|
120
|
-
this._decoder.decode(chunk);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
private OnFrameDecoded(frame: VideoFrame) {
|
|
124
|
-
if (this._framePending) this._framePending.close();
|
|
125
|
-
this._framePending = frame;
|
|
126
|
-
requestAnimationFrame(() => this.RenderFrame());
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private RenderFrame() {
|
|
130
|
-
|
|
131
|
-
let frame = this._framePending;
|
|
132
|
-
this._framePending = undefined;
|
|
133
|
-
|
|
134
|
-
if (frame) {
|
|
135
|
-
|
|
136
|
-
//Set canvas resolution to video resolution
|
|
137
|
-
let canvas = this._canvas!.nativeElement;
|
|
138
|
-
canvas.width = frame.displayWidth;
|
|
139
|
-
canvas.height = frame.displayHeight;
|
|
140
|
-
|
|
141
|
-
//Check for control size change
|
|
142
|
-
let canvasRect = canvas.getBoundingClientRect();
|
|
143
|
-
let canvasSize = new Size(Math.round(canvasRect.width * window.devicePixelRatio), Math.round(canvasRect.height * window.devicePixelRatio));
|
|
144
|
-
if (!Size.AreEqual(canvasSize, this._displaySize)) {
|
|
145
|
-
this._displaySize = canvasSize;
|
|
146
|
-
|
|
147
|
-
console.log(`Resized to ${canvasSize.Width} x ${canvasSize.Height}`);
|
|
148
|
-
let message = new ResizeSurfaceMessage(canvasSize.Width, canvasSize.Height);
|
|
149
|
-
this._streamingService.SendMessage(message);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
//Draw new frame
|
|
153
|
-
if (this._renderingContext) {
|
|
154
|
-
this._renderingContext.drawImage(frame, 0, 0, frame.displayWidth, frame.displayHeight);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
//Release memory
|
|
158
|
-
frame.close();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
public ToggleFullScreen() {
|
|
163
|
-
if (document.fullscreenElement === this._root?.nativeElement) {
|
|
164
|
-
document.exitFullscreen();
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
this._root?.nativeElement.requestFullscreen();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
1
|
+
import { Component, ElementRef, ViewChild } from '@angular/core';
|
|
2
|
+
import { StreamingService } from '../../services/streaming.service';
|
|
3
|
+
import { EncodedFrame, FrameType, Size } from '../../data/frames';
|
|
4
|
+
import { PointerInputMessage, PointerStates, ResizeSurfaceMessage } from '../../data/streaming-messages';
|
|
5
|
+
import { CommonModule } from '@angular/common';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'warpr-stream-host',
|
|
9
|
+
standalone: true,
|
|
10
|
+
imports: [CommonModule],
|
|
11
|
+
templateUrl: './stream-host.component.html',
|
|
12
|
+
styleUrl: './stream-host.component.scss'
|
|
13
|
+
})
|
|
14
|
+
export class StreamHostComponent {
|
|
15
|
+
|
|
16
|
+
@ViewChild("root")
|
|
17
|
+
private _root?: ElementRef<HTMLDivElement>;
|
|
18
|
+
|
|
19
|
+
@ViewChild("renderTarget")
|
|
20
|
+
private _canvas?: ElementRef<HTMLCanvasElement>;
|
|
21
|
+
private _renderingContext: CanvasRenderingContext2D | null = null;
|
|
22
|
+
|
|
23
|
+
private _decoder: VideoDecoder;
|
|
24
|
+
private _framePending?: VideoFrame;
|
|
25
|
+
private _videoSize = Size.Empty;
|
|
26
|
+
private _displaySize = Size.Empty;
|
|
27
|
+
private _isDecoderInitialized = false;
|
|
28
|
+
private _pointerStates: PointerStates = {};
|
|
29
|
+
|
|
30
|
+
public get IsFullScreenAvailable(): boolean {
|
|
31
|
+
return document.fullscreenEnabled;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public constructor(
|
|
35
|
+
private _streamingService: StreamingService) {
|
|
36
|
+
this._streamingService.Connected.Subscribe((sender, eventArgs) => this.OnConnected());
|
|
37
|
+
this._streamingService.FrameReceived.Subscribe((sender, eventArgs) => this.OnFrameReceived(eventArgs));
|
|
38
|
+
|
|
39
|
+
this._decoder = new VideoDecoder({
|
|
40
|
+
output: (frame) => this.OnFrameDecoded(frame),
|
|
41
|
+
error: (error) => console.log(error)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async ngAfterViewInit() {
|
|
46
|
+
|
|
47
|
+
console.log("Initializing stream host...");
|
|
48
|
+
|
|
49
|
+
let canvas = this._canvas!.nativeElement;
|
|
50
|
+
this._renderingContext = canvas.getContext("2d")!;
|
|
51
|
+
canvas.onpointerdown = (event) => this.OnPointerEvent(event);
|
|
52
|
+
canvas.onpointermove = (event) => this.OnPointerEvent(event);
|
|
53
|
+
canvas.onpointerup = (event) => this.OnPointerEvent(event);
|
|
54
|
+
canvas.onwheel = (event) => this.OnWheelEvent(event);
|
|
55
|
+
canvas.oncontextmenu = (event) => event.preventDefault();
|
|
56
|
+
|
|
57
|
+
if (this._renderingContext) {
|
|
58
|
+
console.log("Stream host initialized.");
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.log("Stream host failed to initialize.");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
requestAnimationFrame(() => this.RenderFrame());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private OnPointerEvent(event: PointerEvent, wheelDelta = 0) {
|
|
68
|
+
//console.log(event);
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
|
|
71
|
+
if (event.type == "pointerdown") this._canvas!.nativeElement.setPointerCapture(event.pointerId);
|
|
72
|
+
|
|
73
|
+
let message = new PointerInputMessage(event, this._pointerStates, this._canvas!.nativeElement, wheelDelta);
|
|
74
|
+
//console.log(message);
|
|
75
|
+
|
|
76
|
+
this._streamingService.SendMessage(message);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private OnWheelEvent(event: WheelEvent) {
|
|
80
|
+
//console.log(event);
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
|
|
83
|
+
let pointerEvent: PointerEvent = new PointerEvent(event.type, event);
|
|
84
|
+
this.OnPointerEvent(pointerEvent, event.deltaY);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private OnConnected() {
|
|
88
|
+
this._displaySize = Size.Empty;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private OnFrameReceived(frame: EncodedFrame) {
|
|
92
|
+
let chunk = new EncodedVideoChunk({
|
|
93
|
+
data: frame.Bytes,
|
|
94
|
+
type: frame.Type == FrameType.Key ? "key" : "delta",
|
|
95
|
+
timestamp: frame.Index
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
//Initialize decoder
|
|
99
|
+
let frameSize = new Size(frame.Width, frame.Height);
|
|
100
|
+
|
|
101
|
+
if (!Size.AreEqual(frameSize, this._videoSize)) {
|
|
102
|
+
console.log("Configuring decoder...");
|
|
103
|
+
this._decoder.configure({
|
|
104
|
+
codec: "hvc1.1.6.L93.B0",
|
|
105
|
+
codedWidth: frame.Width,
|
|
106
|
+
codedHeight: frame.Height,
|
|
107
|
+
hardwareAcceleration: "prefer-hardware",
|
|
108
|
+
optimizeForLatency: true
|
|
109
|
+
});
|
|
110
|
+
console.log("Decoder configured.");
|
|
111
|
+
console.log(`Resolution is ${frame.Width} x ${frame.Height}.`);
|
|
112
|
+
|
|
113
|
+
this._videoSize = frameSize;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//Decode frame
|
|
117
|
+
if (!this._isDecoderInitialized && chunk.type != "key") return;
|
|
118
|
+
this._isDecoderInitialized = true;
|
|
119
|
+
|
|
120
|
+
this._decoder.decode(chunk);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private OnFrameDecoded(frame: VideoFrame) {
|
|
124
|
+
if (this._framePending) this._framePending.close();
|
|
125
|
+
this._framePending = frame;
|
|
126
|
+
requestAnimationFrame(() => this.RenderFrame());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private RenderFrame() {
|
|
130
|
+
|
|
131
|
+
let frame = this._framePending;
|
|
132
|
+
this._framePending = undefined;
|
|
133
|
+
|
|
134
|
+
if (frame) {
|
|
135
|
+
|
|
136
|
+
//Set canvas resolution to video resolution
|
|
137
|
+
let canvas = this._canvas!.nativeElement;
|
|
138
|
+
canvas.width = frame.displayWidth;
|
|
139
|
+
canvas.height = frame.displayHeight;
|
|
140
|
+
|
|
141
|
+
//Check for control size change
|
|
142
|
+
let canvasRect = canvas.getBoundingClientRect();
|
|
143
|
+
let canvasSize = new Size(Math.round(canvasRect.width * window.devicePixelRatio), Math.round(canvasRect.height * window.devicePixelRatio));
|
|
144
|
+
if (!Size.AreEqual(canvasSize, this._displaySize)) {
|
|
145
|
+
this._displaySize = canvasSize;
|
|
146
|
+
|
|
147
|
+
console.log(`Resized to ${canvasSize.Width} x ${canvasSize.Height}`);
|
|
148
|
+
let message = new ResizeSurfaceMessage(canvasSize.Width, canvasSize.Height);
|
|
149
|
+
this._streamingService.SendMessage(message);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
//Draw new frame
|
|
153
|
+
if (this._renderingContext) {
|
|
154
|
+
this._renderingContext.drawImage(frame, 0, 0, frame.displayWidth, frame.displayHeight);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//Release memory
|
|
158
|
+
frame.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public ToggleFullScreen() {
|
|
163
|
+
if (document.fullscreenElement === this._root?.nativeElement) {
|
|
164
|
+
document.exitFullscreen();
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
this._root?.nativeElement.requestFullscreen();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
package/src/lib/data/frames.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { ArrayStream } from "../infrastructure/array-stream";
|
|
2
|
-
|
|
3
|
-
export enum FrameType {
|
|
4
|
-
Unknown,
|
|
5
|
-
Key,
|
|
6
|
-
Delta
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class EncodedFrame {
|
|
10
|
-
public Index: number;
|
|
11
|
-
public Type: FrameType;
|
|
12
|
-
public Width: number;
|
|
13
|
-
public Height: number;
|
|
14
|
-
public Bytes: ArrayBuffer;
|
|
15
|
-
|
|
16
|
-
public constructor(buffer: ArrayBuffer) {
|
|
17
|
-
let stream = new ArrayStream(buffer);
|
|
18
|
-
|
|
19
|
-
this.Type = stream.ReadUInt32();
|
|
20
|
-
this.Index = stream.ReadUInt32();
|
|
21
|
-
this.Width = stream.ReadUInt32();
|
|
22
|
-
this.Height = stream.ReadUInt32();
|
|
23
|
-
this.Bytes = stream.ReadBuffer();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class Size {
|
|
28
|
-
public constructor(
|
|
29
|
-
public Width: number,
|
|
30
|
-
public Height: number) { }
|
|
31
|
-
|
|
32
|
-
public static Empty: Size = { Width: 0, Height: 0 };
|
|
33
|
-
|
|
34
|
-
public static AreEqual(a: Size, b: Size) {
|
|
35
|
-
return a.Width === b.Width && a.Height === b.Height;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
1
|
+
import { ArrayStream } from "../infrastructure/array-stream";
|
|
2
|
+
|
|
3
|
+
export enum FrameType {
|
|
4
|
+
Unknown,
|
|
5
|
+
Key,
|
|
6
|
+
Delta
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class EncodedFrame {
|
|
10
|
+
public Index: number;
|
|
11
|
+
public Type: FrameType;
|
|
12
|
+
public Width: number;
|
|
13
|
+
public Height: number;
|
|
14
|
+
public Bytes: ArrayBuffer;
|
|
15
|
+
|
|
16
|
+
public constructor(buffer: ArrayBuffer) {
|
|
17
|
+
let stream = new ArrayStream(buffer);
|
|
18
|
+
|
|
19
|
+
this.Type = stream.ReadUInt32();
|
|
20
|
+
this.Index = stream.ReadUInt32();
|
|
21
|
+
this.Width = stream.ReadUInt32();
|
|
22
|
+
this.Height = stream.ReadUInt32();
|
|
23
|
+
this.Bytes = stream.ReadBuffer();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class Size {
|
|
28
|
+
public constructor(
|
|
29
|
+
public Width: number,
|
|
30
|
+
public Height: number) { }
|
|
31
|
+
|
|
32
|
+
public static Empty: Size = { Width: 0, Height: 0 };
|
|
33
|
+
|
|
34
|
+
public static AreEqual(a: Size, b: Size) {
|
|
35
|
+
return a.Width === b.Width && a.Height === b.Height;
|
|
36
|
+
}
|
|
37
|
+
}
|