Started implementing video support.
This commit is contained in:
8
package-lock.json
generated
8
package-lock.json
generated
@@ -15,7 +15,7 @@
|
||||
"@angular/forms": "^21.2.0",
|
||||
"@angular/platform-browser": "^21.2.0",
|
||||
"@angular/router": "^21.2.0",
|
||||
"@chatenium/chatenium-sdk": "^1.0.11",
|
||||
"@chatenium/chatenium-sdk": "^1.1.2",
|
||||
"@ngx-translate/core": "^17.0.0",
|
||||
"@ngx-translate/http-loader": "^17.0.0",
|
||||
"@taiga-ui/addon-charts": "^5.1.0",
|
||||
@@ -988,9 +988,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@chatenium/chatenium-sdk": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.0.11.tgz",
|
||||
"integrity": "sha512-2vkN+W541bMEdWTStrXorsEmbQ9mva6drKOU11yFdVMzpPEsMJr9bvk5Lwc5COpOgNTIbnFSAkzPg+0USFruVQ==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.1.2.tgz",
|
||||
"integrity": "sha512-MYUdi1zxcsSUlf1JADU7HNU6zxPejNuspbt+9P3iUBI2ecHWzhqSdcQRR+OMEe0UThl7QNIlZrt0yl15/4fjYQ==",
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^10.4.0",
|
||||
"axios": "^1.14.0",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@angular/forms": "^21.2.0",
|
||||
"@angular/platform-browser": "^21.2.0",
|
||||
"@angular/router": "^21.2.0",
|
||||
"@chatenium/chatenium-sdk": "^1.0.11",
|
||||
"@chatenium/chatenium-sdk": "^1.1.2",
|
||||
"@ngx-translate/core": "^17.0.0",
|
||||
"@ngx-translate/http-loader": "^17.0.0",
|
||||
"@taiga-ui/addon-charts": "^5.1.0",
|
||||
|
||||
@@ -49,9 +49,10 @@ export class Dm implements OnInit {
|
||||
fileId: file.fileId,
|
||||
type: file.type,
|
||||
format: file.extension,
|
||||
path: file.preview,
|
||||
path: file.blob,
|
||||
height: file.height,
|
||||
width: file.width,
|
||||
localVideoThumbnail: file.videoThumbnail
|
||||
})
|
||||
})
|
||||
|
||||
@@ -82,46 +83,58 @@ export class Dm implements OnInit {
|
||||
console.log(fileId, allChunks, chunksDone)
|
||||
}
|
||||
|
||||
onWsListen(action: string, message: string) {
|
||||
console.log(action, message)
|
||||
// The chatid parameter ensures isolation
|
||||
onWsListen(action: string, message: string, chatid: string) {
|
||||
const data = JSON.parse(message);
|
||||
if (data.chatid === chatid) {
|
||||
const targetStore = this.serviceManager.dmServices()[chatid];
|
||||
if (targetStore) {
|
||||
switch (action) {
|
||||
case "newMessage": {
|
||||
this.store.messages.update(messages => [...messages, JSON.parse(message)])
|
||||
case "newMessage":
|
||||
targetStore.messages.update(messages => [...messages, data]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.params.subscribe(async params => {
|
||||
const chatid = params['chatid']
|
||||
this.chatid = chatid
|
||||
console.log(this.serviceManager.chats())
|
||||
const session = this.serviceManager.currentSession();
|
||||
const chatData = this.serviceManager.chats().find(chat => chat.chatid == chatid)
|
||||
const chatid = params['chatid'];
|
||||
this.chatid = chatid;
|
||||
|
||||
// Setup storage
|
||||
if (!this.serviceManager.dmServices()[chatid] && session != null && chatData != null) {
|
||||
this.serviceManager.dmServices()[chatid] = {
|
||||
service: new DMService(
|
||||
const session = this.serviceManager.currentSession();
|
||||
const chatData = this.serviceManager.chats().find(c => c.chatid === chatid);
|
||||
|
||||
if (!session || !chatData) {
|
||||
console.warn(`Initialization deferred for ${chatid}: Session or ChatData missing.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.serviceManager.dmServices()[chatid]) {
|
||||
const newStore = {
|
||||
chatData: signal<Chat>(chatData),
|
||||
messages: signal<Message[]>([]),
|
||||
messageBox: new MessageBoxViewModel((msg, files) => this.sendMessage(msg, files)),
|
||||
wsListener: (action, data) => this.onWsListen(action, data, chatid),
|
||||
} as DmStorage;
|
||||
|
||||
newStore.service = new DMService(
|
||||
session.userData.userid,
|
||||
session.token,
|
||||
chatid,
|
||||
this.indexedDb.getApi(),
|
||||
(action, data) => {
|
||||
this.onWsListen(action, data)
|
||||
}
|
||||
),
|
||||
chatData: signal<Chat>(chatData),
|
||||
messages: signal<Message[]>([]),
|
||||
messageBox: new MessageBoxViewModel(
|
||||
(msg, files) => this.sendMessage(msg, files),
|
||||
)
|
||||
}
|
||||
}
|
||||
(action, data) => newStore.wsListener(action, data)
|
||||
);
|
||||
|
||||
this.store.messages.set(await this.serviceManager.dmServices()[chatid].service.get())
|
||||
console.log(WebSocketHandler.getInstance().connId)
|
||||
await this.store.service.joinWebSocketRoom()
|
||||
})
|
||||
this.serviceManager.dmServices()[chatid] = newStore;
|
||||
|
||||
const currentStore = this.serviceManager.dmServices()[chatid];
|
||||
const history = await currentStore.service.get();
|
||||
currentStore.messages.set(history);
|
||||
|
||||
await currentStore.service.joinWebSocketRoom();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<masonry [maxColSize]="2" style="overflow-y: scroll; height: 500px">
|
||||
@for (file of viewModel().files(); track file) {
|
||||
@if (file.type == "image") {
|
||||
<img [src]="file.preview" style="width: 100%; height: 100%; object-fit: fill;"/>
|
||||
<img [src]="file.blob" style="width: 100%; height: 100%; object-fit: fill;"/>
|
||||
}
|
||||
}
|
||||
</masonry>
|
||||
|
||||
@@ -107,22 +107,24 @@ export class MessageBox {
|
||||
for (let i = 0; i < fileList.length; i++) {
|
||||
const file = fileList[i];
|
||||
const type = file.type.split("/").shift() ?? ""
|
||||
let preview = ""
|
||||
let blob = ""
|
||||
let thumbnailBlob = undefined
|
||||
let height = 0
|
||||
let width = 0
|
||||
|
||||
console.log(type)
|
||||
|
||||
blob = URL.createObjectURL(file)
|
||||
if (type == "image") {
|
||||
try {
|
||||
const imgData = await makePicturePreview(file)
|
||||
const imgData = await fetchImageDimensions(file)
|
||||
console.log(imgData)
|
||||
preview = imgData.preview
|
||||
height = imgData.height
|
||||
width = imgData.width
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
} else if (type == "video") {
|
||||
const videoData = await processVideo(file)
|
||||
thumbnailBlob = videoData.thumbnailBlob
|
||||
height = videoData.height
|
||||
width = videoData.width
|
||||
}
|
||||
|
||||
console.log("push")
|
||||
@@ -132,14 +134,15 @@ export class MessageBox {
|
||||
name: file.name,
|
||||
type: type,
|
||||
extension: file.name.split(".").pop() ?? "",
|
||||
preview: preview,
|
||||
blob: blob,
|
||||
height: height,
|
||||
width: width,
|
||||
videoThumbnail: thumbnailBlob,
|
||||
})
|
||||
}
|
||||
this.viewModel().dialogOpen.set(true)
|
||||
|
||||
function makePicturePreview(file: File): Promise<{ preview: string, height: number, width: number }> {
|
||||
function fetchImageDimensions(file: File): Promise<{ height: number, width: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
@@ -147,7 +150,6 @@ export class MessageBox {
|
||||
img.onload = () => {
|
||||
console.log("Loaded image")
|
||||
resolve({
|
||||
preview: objectUrl,
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
});
|
||||
@@ -162,6 +164,37 @@ export class MessageBox {
|
||||
img.src = objectUrl;
|
||||
});
|
||||
}
|
||||
|
||||
async function processVideo(file: File): Promise<{ thumbnailBlob: string, height: number, width: number }> {
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
const video = document.createElement('video');
|
||||
video.src = objectUrl;
|
||||
video.crossOrigin = 'anonymous';
|
||||
video.muted = true;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
video.addEventListener('loadeddata', async () => {
|
||||
video.currentTime = 0;
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx!.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
const thumbnail = canvas.toDataURL('image/jpeg')
|
||||
const thumbnailResp = await fetch(thumbnail);
|
||||
const thumbnailBlob = await thumbnailResp.blob();
|
||||
const thumbnailBlobUrl = URL.createObjectURL(thumbnailBlob);
|
||||
|
||||
resolve({
|
||||
thumbnailBlob: thumbnailBlobUrl,
|
||||
height: video.videoHeight,
|
||||
width: video.videoWidth,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected readonly console = console;
|
||||
@@ -171,7 +204,8 @@ export class MessageBox {
|
||||
* All the extra data for client-side rendering. (With finished messages these are generated on the server)
|
||||
*/
|
||||
export interface FileDataWithPreview extends FileData {
|
||||
preview: string;
|
||||
blob: string;
|
||||
videoThumbnail?: string;
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
@for (file of message.files; track file) {
|
||||
@if (file.type == "image") {
|
||||
<img [src]="file.path" style="width: 100%; height: 100%; max-height: 300px; object-fit: cover; border-radius: 25px"/>
|
||||
} @else if (file.type == "video") {
|
||||
<video-player [src]="file.path"></video-player>
|
||||
}
|
||||
}
|
||||
</masonry>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
.bubble {
|
||||
background: var(--tui-background-neutral-2);
|
||||
max-width: 350px;
|
||||
max-width: 50%;
|
||||
min-width: 250px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
|
||||
@@ -4,12 +4,14 @@ import {Message as NetworkMessage} from '@chatenium/chatenium-sdk/domain/textCha
|
||||
import {ServiceManager} from '../../../service-manager';
|
||||
import {DatePipe} from '@angular/common';
|
||||
import {Masonry} from '../masonry/masonry';
|
||||
import {VideoPlayer} from '../video-player/video-player';
|
||||
|
||||
@Component({
|
||||
selector: 'messages',
|
||||
imports: [
|
||||
DatePipe,
|
||||
Masonry
|
||||
Masonry,
|
||||
VideoPlayer
|
||||
],
|
||||
templateUrl: './messages.html',
|
||||
styleUrl: './messages.scss',
|
||||
|
||||
88
src/app/chat/elements/video-player/video-player.html
Normal file
88
src/app/chat/elements/video-player/video-player.html
Normal file
@@ -0,0 +1,88 @@
|
||||
@if (playerActive) {
|
||||
<div id="player" [style]="'max-width:'+maxWidth+';max-height:'+maxHeight" (mouseover)="controlShowed = true"
|
||||
(mouseleave)="controlShowed = false" #player>
|
||||
<video (mouseout)="showVolRange = false" [class.upScale]="videoFullscreen"
|
||||
[style]="'max-width:'+maxWidth+';height:'+maxHeight" (pause)="videoPlaying = false"
|
||||
(play)="videoPlaying = true" #video (timeupdate)="watched = videoHMSFormat(video.currentTime)"
|
||||
(loadedmetadata)="videoPlayer = video; video.style.display = 'block'; videoLoaded = true"
|
||||
(click)="videoPlaying ? video.pause() : video.play(); videoPlaying = !videoPlaying"
|
||||
(dblclick)="videoFullscreen ? exitFullScreen() : player.requestFullscreen(); videoFullscreen = !videoFullscreen"
|
||||
[src]="src"></video>
|
||||
|
||||
<button tuiButton appearance="icon" class="picInPic" (click)="video.requestPictureInPicture();">
|
||||
<tui-icon icon="@tui.picture-in-picture"></tui-icon>
|
||||
</button>
|
||||
|
||||
<div id="controlsHolder">
|
||||
<div id="controls">
|
||||
<button tuiButton appearance="icon"
|
||||
(click)="videoPlaying ? video.pause() : video.play();">
|
||||
@if (videoPlaying) {
|
||||
<tui-icon icon="@tui.pause"></tui-icon>
|
||||
} @else {
|
||||
<tui-icon icon="@tui.play"></tui-icon>
|
||||
}
|
||||
</button>
|
||||
<span>{{ watched }}</span>
|
||||
<progress class="timeProgress" (click)="jump($event)" [max]="video.duration" [value]="video.currentTime"
|
||||
tuiProgressBar size="xs"></progress>
|
||||
<div>
|
||||
<!-- @if (showVolRange) { -->
|
||||
|
||||
<!-- } -->
|
||||
|
||||
<div class="volumeSetter" (mouseover)="showVolRange = true">
|
||||
<button
|
||||
tuiButton appearance="icon"
|
||||
tuiDropdown
|
||||
tuiDropdownAppearance="neutral"
|
||||
tuiDropdownAuto
|
||||
tuiDropdownDirection="top"
|
||||
tuiDropdownLimitWidth="fixed"
|
||||
tuiIconButton
|
||||
>
|
||||
@if (video.volume == 0) {
|
||||
<tui-icon icon="@tui.volume-off"></tui-icon>
|
||||
} @else if (video.volume > .75) {
|
||||
<tui-icon icon="@tui.volume-2"></tui-icon>
|
||||
} @else if (video.volume >= .50 && video.volume < .74) {
|
||||
<tui-icon icon="@tui.volume-1"></tui-icon>
|
||||
} @else {
|
||||
<tui-icon icon="@tui.volume"></tui-icon>
|
||||
}
|
||||
|
||||
<input
|
||||
*tuiDropdown
|
||||
tuiSlider
|
||||
type="range"
|
||||
value="100"
|
||||
(input)="setVolume(volume)"
|
||||
[(ngModel)]="volume"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button tuiButton appearance="icon"
|
||||
(click)="videoFullscreen ? exitFullScreen() : player.requestFullscreen(); videoFullscreen = !videoFullscreen">
|
||||
@if (videoFullscreen) {
|
||||
<tui-icon icon="@tui.minimize"></tui-icon>
|
||||
} @else {
|
||||
<tui-icon icon="@tui.maximize"></tui-icon>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
} @else {
|
||||
<div class="player_preview">
|
||||
@if (thumbnailOverwrite) {
|
||||
<img [src]="thumbnailOverwrite" />
|
||||
} @else {
|
||||
<oimg [src]="src+'_thumbnail.png'"></oimg>
|
||||
}
|
||||
<button tuiButton (click)="playVideo()">
|
||||
<tui-icon icon="@tui.play"></tui-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
110
src/app/chat/elements/video-player/video-player.scss
Normal file
110
src/app/chat/elements/video-player/video-player.scss
Normal file
@@ -0,0 +1,110 @@
|
||||
#player {
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
|
||||
tui-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.picInPic {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.upScale {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
#controlsHolder {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
#controls {
|
||||
width: 95%;
|
||||
height: 50px;
|
||||
background: var(--tui-background-base-alt);
|
||||
border: 2px solid var(--tui-border-normal);
|
||||
padding: 10px;
|
||||
border-radius: 20px;
|
||||
gap: 5px;
|
||||
display: grid;
|
||||
grid-template-columns: 50px 50px 1fr 50px 50px;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
height: 25px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.volumeSetter {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.player_preview {
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
|
||||
button {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 50px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
tui-dropdown[data-appearance] {
|
||||
min-block-size: 8rem;
|
||||
background: var(--tui-background-base);
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
backdrop-filter: blur(1rem);
|
||||
|
||||
[tuiSlider] {
|
||||
position: absolute;
|
||||
inline-size: 7rem;
|
||||
transform-origin: left;
|
||||
transform: rotate(-90deg) translate(-100%, 1rem) translateY(12px);
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/app/chat/elements/video-player/video-player.spec.ts
Normal file
22
src/app/chat/elements/video-player/video-player.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VideoPlayer } from './video-player';
|
||||
|
||||
describe('VideoPlayer', () => {
|
||||
let component: VideoPlayer;
|
||||
let fixture: ComponentFixture<VideoPlayer>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VideoPlayer],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VideoPlayer);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
102
src/app/chat/elements/video-player/video-player.ts
Normal file
102
src/app/chat/elements/video-player/video-player.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import {Component, Input, ViewEncapsulation} from '@angular/core';
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDropdownDirective,
|
||||
TuiDropdownOpen,
|
||||
TuiDropdownOptionsDirective,
|
||||
TuiIcon,
|
||||
TuiSlider
|
||||
} from '@taiga-ui/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {Oimg} from '../oimg/oimg';
|
||||
import {TuiInputDateRange, TuiProgressBar} from '@taiga-ui/kit';
|
||||
|
||||
@Component({
|
||||
selector: 'video-player',
|
||||
imports: [
|
||||
TuiIcon,
|
||||
FormsModule,
|
||||
TuiButton,
|
||||
Oimg,
|
||||
TuiSlider,
|
||||
TuiProgressBar,
|
||||
TuiDropdownOptionsDirective,
|
||||
TuiDropdownDirective,
|
||||
TuiDropdownOpen,
|
||||
TuiInputDateRange
|
||||
],
|
||||
templateUrl: './video-player.html',
|
||||
styleUrl: './video-player.scss',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class VideoPlayer {
|
||||
@Input() src: string = "";
|
||||
@Input() maxWidth: string = "";
|
||||
@Input() maxHeight: string = "";
|
||||
@Input() thumbnailOverwrite: string | undefined = undefined;
|
||||
|
||||
playerActive = false
|
||||
|
||||
videoLoaded = false
|
||||
|
||||
videoPlayer: HTMLVideoElement | any = "";
|
||||
videoPlaying: boolean = false;
|
||||
videoFullscreen: boolean = false;
|
||||
watched: string = "00:00";
|
||||
controlShowed: boolean = false;
|
||||
videoMuted: boolean = false;
|
||||
videoVolBefMute: number = 0;
|
||||
|
||||
videoHMSFormat(time: any) {
|
||||
let hours: any = Math.round(time / 3600)
|
||||
let minutes: any = Math.round(time / 60 - (hours * 60));
|
||||
let seconds: any = Math.round(time - (minutes * 60));
|
||||
if (seconds < 0) seconds += 60;
|
||||
|
||||
if (hours < 10) hours = `0${hours}`;
|
||||
if (minutes < 10) minutes = `0${minutes}`;
|
||||
if (seconds < 10) seconds = `0${seconds}`;
|
||||
|
||||
return hours == 0 ? `${minutes}:${seconds}` : `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
jump(e: any) {
|
||||
let progressBar = e.target;
|
||||
const clickX = e.clientX - progressBar.getBoundingClientRect().left;
|
||||
const progressBarWidth = progressBar.clientWidth;
|
||||
const progressValue = (clickX / progressBarWidth) * progressBar.max;
|
||||
this.videoPlayer.currentTime = progressValue;
|
||||
progressBar.value = progressValue;
|
||||
}
|
||||
|
||||
volume = 50
|
||||
showVolRange = false
|
||||
setVolume(volume: number) {
|
||||
this.videoPlayer.volume = volume / 100;
|
||||
}
|
||||
|
||||
exitFullScreen() {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
|
||||
handleMute(){
|
||||
if(this.videoMuted) {
|
||||
this.videoPlayer.volume = this.videoVolBefMute / 100;
|
||||
this.videoMuted = false;
|
||||
this.volume = this.videoVolBefMute
|
||||
}else{
|
||||
this.videoPlayer.volume = 0;
|
||||
this.videoMuted = true;
|
||||
this.videoVolBefMute = this.volume;
|
||||
this.volume = 0
|
||||
}
|
||||
}
|
||||
|
||||
playVideo() {
|
||||
this.playerActive = true
|
||||
this.videoPlaying = true;
|
||||
setTimeout(() => {
|
||||
this.videoPlayer.play();
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
@@ -40,4 +40,5 @@ export interface DmStorage {
|
||||
messages: WritableSignal<Message[]>
|
||||
chatData: WritableSignal<Chat>
|
||||
messageBox: MessageBoxViewModel
|
||||
wsListener: (action: string, message: string) => void
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user