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 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
- "name": "warpr",
3
- "version": "0.0.1",
4
- "peerDependencies": {
5
- "@angular/common": "^17.3.0",
6
- "@angular/core": "^17.3.0"
7
- },
8
- "dependencies": {
9
- "tslib": "^2.3.0"
10
- },
11
- "sideEffects": false
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
+ }
@@ -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
+ }