From ccde75f72ae24587ec9c390a3eb92c61b3a0a51d Mon Sep 17 00:00:00 2001 From: jgerstbe Date: Fri, 30 Oct 2020 13:16:40 +0100 Subject: [PATCH] [U] huddle.compo upload stories, get stories for correct standup --- src/app/api/supabase/story.service.ts | 22 ++--- src/app/api/supabase/story.ts | 8 +- src/app/huddle/huddle.component.html | 18 ++-- src/app/huddle/huddle.component.ts | 121 ++++++++++++++----------- src/app/recorder/recorder.component.ts | 24 ++--- 5 files changed, 101 insertions(+), 92 deletions(-) diff --git a/src/app/api/supabase/story.service.ts b/src/app/api/supabase/story.service.ts index 9ff5aa7..27b6dcd 100644 --- a/src/app/api/supabase/story.service.ts +++ b/src/app/api/supabase/story.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; +import { map } from 'rxjs/operators'; import { Story } from './story'; import { SupaService } from './supa.service'; @@ -27,25 +28,20 @@ export class StoryService { if (!this.subscribedStandUpIds.includes(standup_id)) { this.subscribeToStories(standup_id); console.log('getStories - REFRESH', standup_id) - const subject: Subject = new Subject(); this.supa.client.from('story').select() .filter('standup_id', 'eq', standup_id) .then(data => { this.updateStore(data.body); - subject.next(data.body); - subject.complete(); }) .catch(error => { - subject.error(error); - subject.complete(); + console.error(error); }); - return subject.asObservable(); - } else { + } else { console.log('getStories - LOCAL', standup_id) - const stories = Object.values(this.storyMap).filter(e => e.standup_id === standup_id) - console.log('filteredStories', stories) - return of(stories); } + return this.stories.asObservable().pipe( + map(stories => stories.filter(e => e.standup_id === standup_id)) + ); } /** @@ -89,13 +85,17 @@ export class StoryService { // this.storyMap.set(e.id, e); this.storyMap[e.id] = e; }); + console.log('update stories', this.storyMap) this.next(); } /** * Emits local store. */ - next = () => this.stories.next(Object.values(this.storyMap)); + next = () => { + console.log('next', Object.values(this.storyMap)) + this.stories.next(Object.values(this.storyMap)) + }; /** * Retrieve story from local store. diff --git a/src/app/api/supabase/story.ts b/src/app/api/supabase/story.ts index d93208d..3568f35 100644 --- a/src/app/api/supabase/story.ts +++ b/src/app/api/supabase/story.ts @@ -1,13 +1,13 @@ export class Story { - id: number; + id: string; standup_id: number; user_id: string - base64: string; + src: string; created_at: string | Date; - constructor(user_id: string, standup_id: number, base64: string) { + constructor(user_id: string, standup_id: number, src: string) { this.user_id = user_id; this.standup_id = standup_id; - this.base64 = base64; + this.src = src; } } \ No newline at end of file diff --git a/src/app/huddle/huddle.component.html b/src/app/huddle/huddle.component.html index f6f84cd..e7250d4 100644 --- a/src/app/huddle/huddle.component.html +++ b/src/app/huddle/huddle.component.html @@ -12,12 +12,12 @@
-
-
-
-

{{user.name}}

-

{{user.submit_time | date :'dd.MM. HH:mm' }}

+
+
+
+

{{story.user_id}}

+

{{story.created_at | date :'dd.MM. HH:mm' }}

@@ -46,9 +46,9 @@

- {{selectedUser.name}} - - {{selectedUser.submit_time | date :'dd.MM. HH:mm' }} + {{selectedStory.user_id}} - {{selectedStory.created_at | date :'dd.MM. HH:mm' }}

- + \ No newline at end of file diff --git a/src/app/huddle/huddle.component.ts b/src/app/huddle/huddle.component.ts index 77a9d03..90f5bba 100644 --- a/src/app/huddle/huddle.component.ts +++ b/src/app/huddle/huddle.component.ts @@ -1,46 +1,27 @@ -import { Component, OnInit, ViewChild } from "@angular/core"; +import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; +import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { ActivatedRoute } from '@angular/router'; import { NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap"; import { SupabaseAuthUser } from '@supabase/supabase-js'; +import { Subject } from 'rxjs'; +import { take, takeUntil } from 'rxjs/operators'; import { StandUp } from '../api/supabase/standup'; import { StandupService } from '../api/supabase/standup.service'; import { Story } from '../api/supabase/story'; import { StoryService } from '../api/supabase/story.service'; import { SupaService } from '../api/supabase/supa.service'; - @Component({ selector: 'app-huddle', templateUrl: './huddle.component.html', styleUrls: ['./huddle.component.scss'] }) -export class HuddleComponent implements OnInit { +export class HuddleComponent implements OnInit, OnDestroy { @ViewChild("content") content; @ViewChild("activeStory") video; - users = [ - { - name: "Jan", - image: "https://www.supercardating.com/doc/image.rhtm/profile-pic2.jpg", - submit_time: 1601655386668, - story_link: "https://erjb.s3.nl-ams.scw.cloud/ttk_beagle.mp4" - }, - { - name: "Bob", - image: - "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.4oYqJqInuQd2TAlPPdggLgHaHa%26pid%3DApi&f=1", - submit_time: null, - story_link: "https://erjb.s3.nl-ams.scw.cloud/ttk_golden.mp4" - }, - { - name: "Angela", - image: - "https://writestylesonline.com/wp-content/uploads/2019/01/What-To-Wear-For-Your-Professional-Profile-Picture-or-Headshot.jpg", - submit_time: 1601655386668, - story_link: "https://erjb.s3.nl-ams.scw.cloud/ttk_frenchie.mp4" - } - ]; - selectedUser; standup: StandUp; + selectedStory: Story; stories: Story[] = []; + unsubscribe: Subject = new Subject(); constructor( private modalService: NgbModal, @@ -48,17 +29,23 @@ export class HuddleComponent implements OnInit { private storyService: StoryService, private supaService: SupaService, private route: ActivatedRoute, + private sanitizer: DomSanitizer, ) {} ngOnInit() { this.route.params.subscribe( params => { + console.warn('PARAMS', params) + this.reset(); if (params.id) { this.standupService.getOne(params.id).subscribe( data => { console.log(data); this.standup = data; - this.loadStories(data.id).subscribe(stories => this.stories = stories); + this.loadStories(data.id).subscribe(stories => { + console.log('GOT STORIES', stories); + this.stories = stories; + }); }, error => { console.error(error); @@ -67,62 +54,70 @@ export class HuddleComponent implements OnInit { } } ) + this.unsubscribe.subscribe(data => console.warn('UNSUBSCRIBE')) } - playStory(user) { - if (!user.submit_time || !user.story_link) { + ngOnDestroy() { + console.error('HUDDLE COMP DESTROYED') + this.unsubscribe.next(true); + this.unsubscribe.complete(); + } + + reset() { + this.stories = []; + delete this.standup; + delete this.selectedStory; + this.unsubscribe.next(true); + } + + playStory(story) { + if (!story.id || !story.src) { return; } this.modalService.open(this.content, { centered: true }); - this.selectedUser = user; + this.selectedStory = story; } prevUser(modal: NgbModalRef) { - let index: number = this.users.findIndex( - (u) => u.name === this.selectedUser.name + let index: number = this.stories.findIndex( + (u) => u.id === this.selectedStory.id ); if (index < 1) { modal.close(); return; } - while ( - !this.users[index - 1].story_link || - !this.users[index - 1].submit_time - ) { + while (!this.stories[index - 1].id) { index--; if (index === 0) { modal.close(); return; } } - this.selectedUser = this.users[index - 1]; + this.selectedStory = this.stories[index - 1]; } nextUser(modal: NgbModalRef) { - let index: number = this.users.findIndex( - (u) => u.name === this.selectedUser.name + let index: number = this.stories.findIndex( + (u) => u.id === this.selectedStory.id ); - if (index === -1 || index === this.users.length - 1) { + if (index === -1 || index === this.stories.length - 1) { modal.close(); return; } - while ( - !this.users[index + 1].story_link || - !this.users[index + 1].submit_time - ) { - if (index === -1 || index === this.users.length - 1) { + while (!this.stories[index + 1].id) { + if (index === -1 || index === this.stories.length - 1) { modal.close(); return; } index++; } - this.selectedUser = this.users[index + 1]; + this.selectedStory = this.stories[index + 1]; } updateStandUp(name:string, desc:string) { this.standup.name = name; this.standup.description = desc; - this.standupService.updateOne(this.standup).subscribe( + this.standupService.updateOne(this.standup).pipe(take(1)).subscribe( data => { console.log('Success', data); }, @@ -133,7 +128,7 @@ export class HuddleComponent implements OnInit { } deleteStandUp() { - this.standupService.deleteOne(this.standup).subscribe( + this.standupService.deleteOne(this.standup).pipe(take(1)).subscribe( data => { console.log('Success', data); }, @@ -143,12 +138,11 @@ export class HuddleComponent implements OnInit { ); } - async uploadStory(base64: string) { + async uploadStory(src: string) { console.log('uploadStory', event); const user: SupabaseAuthUser = await this.supaService.client.auth.user(); - // TODO ID muss in DB noch autoincremented werden. - const story = new Story(user.id, this.standup.id, base64); - this.storyService.addOne(story).subscribe( + const story = new Story(user.id, this.standup.id, src); + this.storyService.addOne(story).pipe(take(1)).subscribe( data => { console.log('Success', data); }, @@ -159,7 +153,28 @@ export class HuddleComponent implements OnInit { } loadStories(id: number) { - return this.storyService.getStories(id); + return this.storyService.getStories(id).pipe(takeUntil(this.unsubscribe.asObservable())); + } + + base64ToSafeURL(b64Data: string, contentType: string ='video/webm', sliceSize: number=512): SafeUrl { + // https://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript#16245768 + const byteCharacters = atob(b64Data.split('base64,')[1]); + const byteArrays = []; + + for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { + const slice = byteCharacters.slice(offset, offset + sliceSize); + + const byteNumbers = new Array(slice.length); + for (let i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i); + } + + const byteArray = new Uint8Array(byteNumbers); + byteArrays.push(byteArray); + } + + const blob = new Blob(byteArrays, {type: contentType}); + return this.sanitizer.bypassSecurityTrustUrl(URL.createObjectURL(blob)); } } diff --git a/src/app/recorder/recorder.component.ts b/src/app/recorder/recorder.component.ts index ae0a18c..63c8b59 100644 --- a/src/app/recorder/recorder.component.ts +++ b/src/app/recorder/recorder.component.ts @@ -39,9 +39,13 @@ export class RecorderComponent implements OnInit { } startRecording() { + if (!this.stream) { + return; + } this.isRecording = true; this.recorder = new rrtc.RecordRTCPromisesHandler(this.stream, { type: 'video', + mimeType: 'video/webm;codecs=vp8', }); this.recorder.startRecording(); } @@ -50,21 +54,11 @@ export class RecorderComponent implements OnInit { const that = this; this.isRecording = false; await this.recorder.stopRecording(); - let blob = await this.recorder.getBlob(); - // read as b64 - const reader = new FileReader(); - reader.readAsDataURL(blob); - reader.onloadend = function() { - const base64data:string = String(reader.result); - console.log(base64data); - that.recordingEnded.emit(base64data); - that.modal.close(); - } - const handleError = (error) => { - console.error(error); - } - reader.onerror = handleError; - reader.onabort = handleError; + let blob = await this.recorder.getDataURL(); + that.recordingEnded.emit(blob); + that.modal.close(); + // rrtc.invokeSaveAsDialog(blob, 'Recorded-Video.webm'); + return; } } \ No newline at end of file