🚨 cleanup
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
version: "3.7"
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
gate:
|
gate:
|
||||||
image: nginx:latest
|
image: registry.gitlab.com/standa-fifik/battle-caster:latest
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import { ChromecastReceiver } from './model/ChromecastListener';
|
|||||||
const app = createApp(MapApp)
|
const app = createApp(MapApp)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
app.provide(ReceiverSymbol, new MultiReceiver([
|
app.provide(ReceiverSymbol, new MultiReceiver([
|
||||||
|
// @ts-ignore
|
||||||
new BroadcastReceiver(),
|
new BroadcastReceiver(),
|
||||||
|
// @ts-ignore
|
||||||
new ChromecastReceiver(),
|
new ChromecastReceiver(),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
|
|||||||
@@ -56,14 +56,13 @@ export class ChromecastSender implements Sender {
|
|||||||
this.dataChannel.addEventListener('message', (event) => {
|
this.dataChannel.addEventListener('message', (event) => {
|
||||||
console.log('data channel message', event.data);
|
console.log('data channel message', event.data);
|
||||||
});
|
});
|
||||||
window['webrtcPeer'] = this.webrtcPeer;
|
|
||||||
window['dataChannel'] = this.dataChannel;
|
|
||||||
const offer = await this.webrtcPeer.createOffer();
|
const offer = await this.webrtcPeer.createOffer();
|
||||||
await this.webrtcPeer.setLocalDescription(offer);
|
await this.webrtcPeer.setLocalDescription(offer);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.sendMessage({ type: 'RTC_OFFER', offer });
|
this.sendMessage({ type: 'RTC_OFFER', offer });
|
||||||
this.webrtcPeer.onicecandidate = (event: any) => {
|
this.webrtcPeer.onicecandidate = (event: any) => {
|
||||||
if (event.candidate) {
|
if (event.candidate) {
|
||||||
|
// @ts-ignore
|
||||||
this.sendMessage({ type: 'RTC_ICE', candidate: event.candidate });
|
this.sendMessage({ type: 'RTC_ICE', candidate: event.candidate });
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
console.log('sending ice candidate', event.candidate);
|
console.log('sending ice candidate', event.candidate);
|
||||||
@@ -159,7 +158,7 @@ export class ChromecastSender implements Sender {
|
|||||||
export class ChromecastReceiver extends EventTarget implements Receiver {
|
export class ChromecastReceiver extends EventTarget implements Receiver {
|
||||||
|
|
||||||
private webrtcPeer?: RTCPeerConnection
|
private webrtcPeer?: RTCPeerConnection
|
||||||
private dataChannel: RTCDataChannel;
|
private dataChannel?: RTCDataChannel;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -189,7 +188,7 @@ export class ChromecastReceiver extends EventTarget implements Receiver {
|
|||||||
this.dispatchEvent(new CustomEvent(msg.type, {detail: msg}));
|
this.dispatchEvent(new CustomEvent(msg.type, {detail: msg}));
|
||||||
this.dispatchEvent(new CustomEvent('all', {detail: msg}));
|
this.dispatchEvent(new CustomEvent('all', {detail: msg}));
|
||||||
// log('data channel message', event.data);
|
// log('data channel message', event.data);
|
||||||
this.dataChannel.send(JSON.stringify({ type: 'ACK', data: 'Hello, sender!' }));
|
this.dataChannel?.send(JSON.stringify({ type: 'ACK', data: 'Hello, sender!' }));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log('data channel error', err);
|
log('data channel error', err);
|
||||||
}
|
}
|
||||||
@@ -210,15 +209,16 @@ export class ChromecastReceiver extends EventTarget implements Receiver {
|
|||||||
|
|
||||||
private initializeCastApi() {
|
private initializeCastApi() {
|
||||||
try {
|
try {
|
||||||
|
// @ts-ignore
|
||||||
const context = cast.framework.CastReceiverContext.getInstance();
|
const context = cast.framework.CastReceiverContext.getInstance();
|
||||||
|
// @ts-ignore
|
||||||
const options = new cast.framework.CastReceiverOptions();
|
const options = new cast.framework.CastReceiverOptions();
|
||||||
const playerManager = context.getPlayerManager();
|
|
||||||
|
|
||||||
context.addCustomMessageListener(CHANNEL, (event) => {
|
context.addCustomMessageListener(CHANNEL, (event: any) => {
|
||||||
const msg = event.data;
|
const msg = event.data;
|
||||||
log('msg', event.data.type);
|
log('msg', event.data.type);
|
||||||
if (msg.type === 'RTC_OFFER') {
|
if (msg.type === 'RTC_OFFER') {
|
||||||
this.initializeWebRTC(msg.offer, (data) => context.sendCustomMessage(CHANNEL, event.senderId, data));
|
this.initializeWebRTC(msg.offer, (data: any) => context.sendCustomMessage(CHANNEL, event.senderId, data));
|
||||||
} else if (msg.type === 'RTC_ICE') {
|
} else if (msg.type === 'RTC_ICE') {
|
||||||
this.webrtcPeer?.addIceCandidate(msg.candidate);
|
this.webrtcPeer?.addIceCandidate(msg.candidate);
|
||||||
} else {
|
} else {
|
||||||
@@ -233,6 +233,7 @@ export class ChromecastReceiver extends EventTarget implements Receiver {
|
|||||||
options.maxInactivity = 3600; // Development only
|
options.maxInactivity = 3600; // Development only
|
||||||
context.start(options);
|
context.start(options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// @ts-ignore
|
||||||
log('initializeCastApi error', err.stack);
|
log('initializeCastApi error', err.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export class FileManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractMetaFromFileName(name: string): { width: number, height: number } {
|
private extractMetaFromFileName(name: string): { width: number, height: number } | undefined {
|
||||||
const czepeku = new RegExp(/\[([0-9]+)x([0-9]+)\]/);
|
const czepeku = new RegExp(/\[([0-9]+)x([0-9]+)\]/);
|
||||||
const czepekuMatches = name?.match(czepeku);
|
const czepekuMatches = name?.match(czepeku);
|
||||||
if (czepekuMatches) {
|
if (czepekuMatches) {
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { InjectionKey } from "vue";
|
import { InjectionKey } from "vue";
|
||||||
import type { FileManager } from "./FileManager";
|
import type { FileManager } from "./FileManager";
|
||||||
import type { CastSender } from "./CastSender.ts_";
|
|
||||||
import type { CastReceiver } from "./CastReceiver.ts_";
|
|
||||||
import { Receiver, Sender } from "./CommunicationChannel";
|
import { Receiver, Sender } from "./CommunicationChannel";
|
||||||
|
|
||||||
|
|
||||||
export const FileManagerSymbol = Symbol("FileManager") as InjectionKey<FileManager>;
|
export const FileManagerSymbol = Symbol("FileManager") as InjectionKey<FileManager>;
|
||||||
export const CastSenderSymbol = Symbol("CastSender") as InjectionKey<CastSender>;
|
|
||||||
export const CastReceiverSymbol = Symbol("CastReceiver") as InjectionKey<CastReceiver>;
|
|
||||||
export const CastTypeSymbol = Symbol("CastType") as InjectionKey<"sender" | "receiver">;
|
export const CastTypeSymbol = Symbol("CastType") as InjectionKey<"sender" | "receiver">;
|
||||||
export const SenderSymbol = Symbol('Sender') as InjectionKey<Sender>;
|
export const SenderSymbol = Symbol('Sender') as InjectionKey<Sender>;
|
||||||
export const ReceiverSymbol = Symbol('Receiver') as InjectionKey<Receiver>;
|
export const ReceiverSymbol = Symbol('Receiver') as InjectionKey<Receiver>;
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
<input type="number" v-model="imageWidth" />
|
<input type="number" v-model="imageWidth" />
|
||||||
<label>Screen Diameter</label>
|
<label>Screen Diameter</label>
|
||||||
<input type="number" v-model="screenDiameterInch" />
|
<input type="number" v-model="screenDiameterInch" />
|
||||||
<div>DPI: {{ screenDPI }}</div>
|
|
||||||
<label>Flip <input type="checkbox" v-model="flipped"></label>
|
<label>Flip <input type="checkbox" v-model="flipped"></label>
|
||||||
<label>Position (X/Y)</label>
|
<label>Position (X/Y)</label>
|
||||||
<input type="range" min="0" max="100" v-model="positionX" />
|
<input type="range" min="0" max="100" v-model="positionX" />
|
||||||
@@ -54,7 +53,7 @@ watch(() => screenDiameterInch.value, (value) => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => [positionX.value, positionY.value, flipped.value], (value) => {
|
watch(() => [positionX.value, positionY.value, flipped.value], () => {
|
||||||
sender?.sendMessage({
|
sender?.sendMessage({
|
||||||
type: 'changePosition',
|
type: 'changePosition',
|
||||||
x: positionX.value,
|
x: positionX.value,
|
||||||
@@ -79,6 +78,9 @@ function openMapWindow() {
|
|||||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
function onStreamImage() {
|
function onStreamImage() {
|
||||||
|
if (!openedFile.value?.path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const image = new Image()
|
const image = new Image()
|
||||||
image.onload = async () => {
|
image.onload = async () => {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<fieldset>
|
|
||||||
<legend>Image settings</legend>
|
|
||||||
<label>Width</label>
|
|
||||||
<input type="number" v-model="imageWidth" />
|
|
||||||
<label>Screen Diameter</label>
|
|
||||||
<input type="number" v-model="screenDiameterInch" />
|
|
||||||
<div>DPI: {{ screenDPI }}</div>
|
|
||||||
<div style="max-width: 32px; height: 32px; overflow: hidden;">
|
|
||||||
<google-cast-launcher style="max-width: 2rem" ></google-cast-launcher>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<FileManager @openFile="openFile" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import FileManager from '../../components/FileManager/FileManager.vue';
|
|
||||||
import type { FileWithMeta } from '../../model/FileManager';
|
|
||||||
import {ref, computed, inject, watch} from 'vue';
|
|
||||||
import {CastTypeSymbol, CastReceiverSymbol, CastSenderSymbol} from '../../model/injectionSymbols';
|
|
||||||
|
|
||||||
const openedFile = ref<FileWithMeta>()
|
|
||||||
const castType = inject(CastTypeSymbol);
|
|
||||||
const receiver = inject(CastReceiverSymbol);
|
|
||||||
const sender = inject(CastSenderSymbol);
|
|
||||||
const canvas = ref<HTMLCanvasElement>();
|
|
||||||
|
|
||||||
const imageWidth = ref(20);
|
|
||||||
const screenDiameterInch = ref(65);
|
|
||||||
|
|
||||||
const screenDPI = computed(() => {
|
|
||||||
const diaPx = Math.sqrt(window.screen.width**2 + window.screen.height ** 2);
|
|
||||||
return diaPx / screenDiameterInch.value;
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => imageWidth.value, (value) => {
|
|
||||||
if (sender) {
|
|
||||||
sender.sendMessage({
|
|
||||||
type: 'imageWidth',
|
|
||||||
imageWidth: value
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function openFile(file: FileWithMeta) {
|
|
||||||
console.log('open file', file)
|
|
||||||
openedFile.value = file
|
|
||||||
imageWidth.value = file.width ?? 20;
|
|
||||||
if (sender) {
|
|
||||||
sender.sendMessage({
|
|
||||||
type: 'loadMap',
|
|
||||||
file
|
|
||||||
})
|
|
||||||
|
|
||||||
const img = new Image();
|
|
||||||
|
|
||||||
img.onload = () => {
|
|
||||||
canvas.value!.width = img.naturalWidth;
|
|
||||||
canvas.value!.height = img.naturalHeight;
|
|
||||||
const ctx = canvas.value!.getContext('2d')!;
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
const msgs = [];
|
|
||||||
const chunkWidth = 32;
|
|
||||||
const chunkHeight = 32;
|
|
||||||
|
|
||||||
for (let x = 0; x< 1920; x += chunkWidth) {
|
|
||||||
for (let y = 0; y < 1080; y += chunkHeight) {
|
|
||||||
const data = ctx.getImageData(x, y, chunkWidth, chunkHeight);
|
|
||||||
const msg = {
|
|
||||||
type: 'imageData',
|
|
||||||
imageData: {
|
|
||||||
data: [...data.data],
|
|
||||||
width: data.width,
|
|
||||||
height: data.height,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
msgs.push(msg);
|
|
||||||
sender.sendMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.clearRect(0, 0, img.naturalWidth, img.naturalHeight);
|
|
||||||
for (const msg of msgs) {
|
|
||||||
loadImageData(msg);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
img.src = file.path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadImageData(detail) {
|
|
||||||
const imageData = new ImageData(
|
|
||||||
new Uint8ClampedArray(detail.imageData.data),
|
|
||||||
detail.imageData.width,
|
|
||||||
detail.imageData.height
|
|
||||||
)
|
|
||||||
const ctx = canvas.value!.getContext('2d')!;
|
|
||||||
ctx.putImageData(imageData, detail.imageData.x, detail.imageData.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (receiver) {
|
|
||||||
receiver.addEventListener('message', ({detail}) => {
|
|
||||||
if (detail.type === 'loadMap') {
|
|
||||||
openFile(detail.file);
|
|
||||||
}
|
|
||||||
if (detail.type === 'imageWidth') {
|
|
||||||
imageWidth.value = detail.imageWidth;
|
|
||||||
}
|
|
||||||
if (detail.type === 'imageData') {
|
|
||||||
loadImageData(detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.aside {
|
|
||||||
position: fixed;
|
|
||||||
overflow: auto;
|
|
||||||
width: 300px;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: auto;
|
|
||||||
right:0;
|
|
||||||
background: rgba(0,0,0,0.8);
|
|
||||||
border-left: #555 2px solid;
|
|
||||||
backdrop-filter: blur(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -22,7 +22,6 @@ import { computed, defineProps, inject, ref, watch } from 'vue';
|
|||||||
import {ReceiverSymbol} from '../../model/injectionSymbols';
|
import {ReceiverSymbol} from '../../model/injectionSymbols';
|
||||||
|
|
||||||
const video = ref<HTMLVideoElement>();
|
const video = ref<HTMLVideoElement>();
|
||||||
const image = ref<HTMLImageElement>();
|
|
||||||
const canvas = ref<HTMLCanvasElement>();
|
const canvas = ref<HTMLCanvasElement>();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -37,12 +36,7 @@ const isImage = computed(() => props.src?.endsWith('.png') || props.src?.endsWit
|
|||||||
const isVideo = computed(() => props.src?.endsWith('.mp4') || props.src?.endsWith('.webm'));
|
const isVideo = computed(() => props.src?.endsWith('.mp4') || props.src?.endsWith('.webm'));
|
||||||
|
|
||||||
const mediaDimensions = ref({width: 0, height: 0});
|
const mediaDimensions = ref({width: 0, height: 0});
|
||||||
const virtualMediaDimensions = ref({width: 0, height: 0});
|
|
||||||
|
|
||||||
const windowSize = {
|
|
||||||
width: window.innerWidth,
|
|
||||||
height: window.innerHeight,
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(() => props.src, (src) => {
|
watch(() => props.src, (src) => {
|
||||||
if (isImage.value) {
|
if (isImage.value) {
|
||||||
@@ -113,14 +107,6 @@ const dynamicStyle = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function onImageLoaded(event: Event) {
|
|
||||||
console.log('image loaded', event.target);
|
|
||||||
mediaDimensions.value = {
|
|
||||||
width: image.value!.naturalWidth,
|
|
||||||
height: image.value!.naturalHeight,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVideoLoaded(event: Event) {
|
function onVideoLoaded(event: Event) {
|
||||||
console.log('video loaded', event.target);
|
console.log('video loaded', event.target);
|
||||||
mediaDimensions.value = {
|
mediaDimensions.value = {
|
||||||
@@ -129,12 +115,6 @@ function onVideoLoaded(event: Event) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateImageSize() {
|
|
||||||
if (image.value) {
|
|
||||||
image.value.width = props.width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.map-scene {
|
.map-scene {
|
||||||
|
|||||||
Reference in New Issue
Block a user