audio & video viewer are added

This commit is contained in:
병준 박 2019-11-07 15:46:02 +09:00
parent 7d612ff208
commit 8066acc2bd
15 changed files with 416 additions and 51 deletions

View File

@ -109,7 +109,7 @@
(moreEvent)="onMoreEvent($event)" (moreEvent)="onMoreEvent($event)"
(massDetail)="onMassDetail($event)" (massDetail)="onMassDetail($event)"
(save)="onSave($event)" (save)="onSave($event)"
(imageViewer)="onImageViewer($event)" (fileViewer)="onFileViewer($event)"
(contextMenu)="onContextMenuMessage($event)" (contextMenu)="onContextMenuMessage($event)"
> >
</ucap-chat-messages> </ucap-chat-messages>

View File

@ -470,8 +470,8 @@ export class MessagesComponent implements OnInit, OnDestroy, AfterViewInit {
); );
} }
async onImageViewer(fileInfo: FileEventJson) { async onFileViewer(fileInfo: FileEventJson) {
this.logger.debug('imageViewer', fileInfo); this.logger.debug('onFileViewer', fileInfo);
const result = await this.dialogService.open< const result = await this.dialogService.open<
FileViewerDialogComponent, FileViewerDialogComponent,
FileViewerDialogData, FileViewerDialogData,

View File

@ -6,6 +6,7 @@
*ngSwitchCase="FileType.File" *ngSwitchCase="FileType.File"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[expired]="getExpiredFile()" [expired]="getExpiredFile()"
(click)="onClickFileViewer(fileInfo)"
(save)="onSave($event)" (save)="onSave($event)"
> >
</ucap-chat-message-box-attach-file> </ucap-chat-message-box-attach-file>
@ -13,6 +14,7 @@
*ngSwitchCase="FileType.Sound" *ngSwitchCase="FileType.Sound"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[expired]="getExpiredFile()" [expired]="getExpiredFile()"
(click)="onClickFileViewer(fileInfo)"
(save)="onSave($event)" (save)="onSave($event)"
> >
</ucap-chat-message-box-attach-file> </ucap-chat-message-box-attach-file>
@ -20,13 +22,13 @@
*ngSwitchCase="FileType.Image" *ngSwitchCase="FileType.Image"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[expired]="getExpiredFile()" [expired]="getExpiredFile()"
(click)="onClickImageViewer(fileInfo)" (click)="onClickFileViewer(fileInfo)"
></ucap-chat-message-box-image> ></ucap-chat-message-box-image>
<ucap-chat-message-box-video <ucap-chat-message-box-video
*ngSwitchCase="FileType.Video" *ngSwitchCase="FileType.Video"
[fileInfo]="fileInfo" [fileInfo]="fileInfo"
[expired]="getExpiredFile()" [expired]="getExpiredFile()"
(click)="onClickImageViewer(fileInfo)" (click)="onClickFileViewer(fileInfo)"
></ucap-chat-message-box-video> ></ucap-chat-message-box-video>
<ucap-chat-message-box-text <ucap-chat-message-box-text
*ngSwitchDefault *ngSwitchDefault

View File

@ -22,7 +22,7 @@ export class FileComponent implements OnInit {
@Output() @Output()
save = new EventEmitter<{ fileInfo: FileEventJson; type: string }>(); save = new EventEmitter<{ fileInfo: FileEventJson; type: string }>();
@Output() @Output()
imageViewer = new EventEmitter<FileEventJson>(); fileViewer = new EventEmitter<FileEventJson>();
fileInfo?: FileEventJson; fileInfo?: FileEventJson;
errorMessage?: string; errorMessage?: string;
@ -50,8 +50,8 @@ export class FileComponent implements OnInit {
} }
} }
onClickImageViewer(fileInfo: FileEventJson) { onClickFileViewer(fileInfo: FileEventJson) {
this.imageViewer.emit(fileInfo); this.fileViewer.emit(fileInfo);
} }
onSave(value: string) { onSave(value: string) {

View File

@ -111,7 +111,7 @@
[eventInfoStatus]="eventInfoStatus" [eventInfoStatus]="eventInfoStatus"
[message]="message" [message]="message"
(save)="onSave($event)" (save)="onSave($event)"
(imageViewer)="onImageViewer($event)" (fileViewer)="onFileViewer($event)"
(contextmenu)="onContextMenuMessage($event, message)" (contextmenu)="onContextMenuMessage($event, message)"
> >
</ucap-chat-message-box-file> </ucap-chat-message-box-file>

View File

@ -46,7 +46,7 @@ export class MessagesComponent implements OnInit {
@Output() @Output()
massDetail = new EventEmitter<number>(); massDetail = new EventEmitter<number>();
@Output() @Output()
imageViewer = new EventEmitter<FileEventJson>(); fileViewer = new EventEmitter<FileEventJson>();
@Output() @Output()
save = new EventEmitter<{ fileInfo: FileInfo; type: string }>(); save = new EventEmitter<{ fileInfo: FileInfo; type: string }>();
@Output() @Output()
@ -158,8 +158,8 @@ export class MessagesComponent implements OnInit {
} }
/** [Event] Image Viewer */ /** [Event] Image Viewer */
onImageViewer(fileInfo: FileEventJson) { onFileViewer(fileInfo: FileEventJson) {
this.imageViewer.emit(fileInfo); this.fileViewer.emit(fileInfo);
} }
/** [Event] Attach File Save & Save As */ /** [Event] Attach File Save & Save As */

View File

@ -38,6 +38,9 @@ export class FileViewerComponent implements OnInit {
case FileType.Video: case FileType.Video:
return FileViewerType.Video; return FileViewerType.Video;
default: default:
if (this.isSoundFile(fileInfo.fileExt)) {
return FileViewerType.Sound;
}
return FileViewerType.Binary; return FileViewerType.Binary;
} }
} }
@ -48,4 +51,8 @@ export class FileViewerComponent implements OnInit {
onClosedViewer(): void { onClosedViewer(): void {
this.closed.emit(); this.closed.emit();
} }
private isSoundFile(fileExt: string): boolean {
return -1 !== ['mp3'].indexOf(fileExt);
}
} }

View File

@ -1,18 +1,84 @@
<div class="ucap-image-viewer-container"> <div class="ucap-sound-viewer-container">
<mat-toolbar color="primary"> <mat-toolbar color="accent" class="ucap-sound-viewer-header">
<span>Third Line</span> <mat-icon class="ucap-sound-viewer-icon">music_note</mat-icon>
<span class="ucap-image-viewer-spacer"></span> <span class="ucap-sound-viewer-title">{{ fileInfo.fileName }}</span>
<mat-icon <span class="ucap-sound-viewer-spacer"></span>
class="example-icon" <button
aria-hidden="false" mat-icon-button
aria-label="Example heart icon" class="ucap-sound-viewer-action"
>favorite</mat-icon matTooltip="다운로드"
matTooltipPosition="below"
aria-label=""
(click)="onClickDownload()"
> >
<mat-icon <mat-icon>get_app</mat-icon>
class="example-icon" </button>
aria-hidden="false" <button
aria-label="Example delete icon" mat-raised-button
>delete</mat-icon color="primary"
class="ucap-sound-viewer-action"
(click)="onClickClose()"
> >
Close
</button>
</mat-toolbar> </mat-toolbar>
<div class="ucap-sound-viewer-body">
<div
class="ucap-sound-viewer-sound-icon"
fxLayout="row"
fxLayout.xs="column"
fxLayoutAlign="center center"
>
<audio
[src]="fileDownloadUrl"
#audioPlayer
(playing)="onPlayingAudio()"
(pause)="onPauseAudio()"
(timeupdate)="onTimeUpdateAudio()"
(volumechange)="onVolumeChangeAudio()"
(loadstart)="onLoadStartAudio()"
(loadeddata)="onLoadedDataAudio()"
></audio>
<mat-icon class="ucap-sound-viewer-icon">music_note</mat-icon>
</div>
<div
class="ucap-sound-viewer-sound-time"
fxLayout="row"
fxLayout.xs="column"
fxLayoutAlign="center center"
>
<mat-slider
#timeSlider
min="0"
[max]="duration"
[value]="currentTime"
(change)="onChangeTimeSlider($event)"
>
</mat-slider>
</div>
<div
class="ucap-sound-viewer-sound-controls"
fxLayout="row"
fxLayout.xs="column"
fxLayoutAlign="center center"
>
<div class="ucap-sound-viewer-sound-time-current">
{{ currentTime | ucapSecondsToMinutes }}
</div>
<span class="ucap-sound-viewer-spacer"></span>
<button
mat-icon-button
class="ucap-sound-viewer-action"
[matTooltip]="playing ? '멈춤' : '재생'"
aria-label=""
(click)="onClickPlayOrPause()"
>
<mat-icon>{{ playing ? 'pause' : 'play_arrow' }}</mat-icon>
</button>
<span class="ucap-sound-viewer-spacer"></span>
<div class="ucap-sound-viewer-sound-time-total">
{{ duration | ucapSecondsToMinutes }}
</div>
</div>
</div>
</div> </div>

View File

@ -6,8 +6,48 @@
width: 100%; width: 100%;
height: 50px; height: 50px;
.ucap-sound-viewer-icon {
}
.ucap-sound-viewer-title {
}
}
.ucap-sound-viewer-body {
position: relative;
background-color: white;
width: 100%;
height: 100%;
padding-bottom: 70px;
.ucap-sound-viewer-sound-icon {
width: 100%;
height: calc(100% - 60px);
}
.ucap-sound-viewer-sound-time {
width: 100%;
height: 30px;
}
.ucap-sound-viewer-sound-controls {
width: 100%;
height: 30px;
.ucap-sound-viewer-sound-time-current {
padding-left: 30px;
}
.ucap-sound-viewer-sound-time-total {
padding-right: 30px;
}
}
}
.ucap-sound-viewer-spacer { .ucap-sound-viewer-spacer {
flex: 1 1 auto; flex: 1 1 auto;
} }
.ucap-sound-viewer-action {
} }
} }
mat-slider {
width: 95%;
}

View File

@ -1,7 +1,15 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import {
Component,
OnInit,
Input,
Output,
EventEmitter,
ViewChild,
ElementRef
} from '@angular/core';
import { ucapAnimations } from '../../animations'; import { ucapAnimations } from '../../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { DeviceType } from '@ucap-webmessenger/core'; import { MatSlider, MatSliderChange } from '@angular/material';
@Component({ @Component({
selector: 'ucap-sound-viewer', selector: 'ucap-sound-viewer',
@ -22,11 +30,68 @@ export class SoundViewerComponent implements OnInit {
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ViewChild('audioPlayer', { static: true })
audioPlayer: ElementRef<HTMLAudioElement>;
@ViewChild('timeSlider', { static: true })
timeSlider: MatSlider;
playing = false;
duration = 0.01;
currentTime = 0;
volume = 0.1;
loading = false;
constructor() {} constructor() {}
ngOnInit() {} ngOnInit() {}
onClickDownload(): void {} onClickPlayOrPause(): void {
if (this.loading) {
return;
}
if (this.audioPlayer.nativeElement.paused) {
this.audioPlayer.nativeElement.play();
} else {
this.currentTime = this.audioPlayer.nativeElement.currentTime;
this.audioPlayer.nativeElement.pause();
}
}
onChangeTimeSlider(e: MatSliderChange): void {
console.log('onChangeTimeSlider', e.value);
this.audioPlayer.nativeElement.currentTime = e.value;
}
onPlayingAudio(): void {
this.playing = true;
// this.duration = Math.floor(this.audioPlayer.nativeElement.duration);
}
onPauseAudio(): void {
this.playing = false;
}
onTimeUpdateAudio(): void {
this.currentTime = Math.floor(this.audioPlayer.nativeElement.currentTime);
}
onVolumeChangeAudio(): void {
this.volume = Math.floor(this.audioPlayer.nativeElement.volume);
}
onLoadStartAudio(): void {
this.loading = true;
}
onLoadedDataAudio(): void {
this.loading = false;
this.duration = Math.floor(this.audioPlayer.nativeElement.duration);
}
onClickDownload(): void {
this.download.emit();
}
onClickClose(): void { onClickClose(): void {
this.closed.emit(); this.closed.emit();

View File

@ -1,18 +1,84 @@
<div class="ucap-image-viewer-container"> <div class="ucap-video-viewer-container">
<mat-toolbar color="primary"> <mat-toolbar color="accent" class="ucap-video-viewer-header">
<span>Third Line</span> <mat-icon class="ucap-video-viewer-icon">video_label</mat-icon>
<span class="ucap-image-viewer-spacer"></span> <span class="ucap-video-viewer-title">{{ fileInfo.fileName }}</span>
<mat-icon <span class="ucap-video-viewer-spacer"></span>
class="example-icon" <button
aria-hidden="false" mat-icon-button
aria-label="Example heart icon" class="ucap-video-viewer-action"
>favorite</mat-icon matTooltip="다운로드"
matTooltipPosition="below"
aria-label=""
(click)="onClickDownload()"
> >
<mat-icon <mat-icon>get_app</mat-icon>
class="example-icon" </button>
aria-hidden="false"
aria-label="Example delete icon" <button
>delete</mat-icon mat-raised-button
color="primary"
class="ucap-video-viewer-action"
(click)="onClickClose()"
> >
Close
</button>
</mat-toolbar> </mat-toolbar>
<div class="ucap-video-viewer-body">
<div
class="ucap-video-viewer-video-icon"
fxLayout="row"
fxLayout.xs="column"
fxLayoutAlign="center center"
>
<video
[src]="fileDownloadUrl"
#audioPlayer
(playing)="onPlayingVideo()"
(pause)="onPauseVideo()"
(timeupdate)="onTimeUpdateVideo()"
(volumechange)="onVolumeChangeVideo()"
(loadstart)="onLoadStartVideo()"
(loadeddata)="onLoadedDataVideo()"
></video>
</div>
<div
class="ucap-video-viewer-video-time"
fxLayout="row"
fxLayout.xs="column"
fxLayoutAlign="center center"
>
<mat-slider
#timeSlider
min="0"
[max]="duration"
[value]="currentTime"
(change)="onChangeTimeSlider($event)"
>
</mat-slider>
</div>
<div
class="ucap-video-viewer-video-controls"
fxLayout="row"
fxLayout.xs="column"
fxLayoutAlign="center center"
>
<div class="ucap-video-viewer-video-time-current">
{{ currentTime | ucapSecondsToMinutes }}
</div>
<span class="ucap-video-viewer-spacer"></span>
<button
mat-icon-button
class="ucap-video-viewer-action"
[matTooltip]="playing ? '멈춤' : '재생'"
aria-label=""
(click)="onClickPlayOrPause()"
>
<mat-icon>{{ playing ? 'pause' : 'play_arrow' }}</mat-icon>
</button>
<span class="ucap-video-viewer-spacer"></span>
<div class="ucap-video-viewer-video-time-total">
{{ duration | ucapSecondsToMinutes }}
</div>
</div>
</div>
</div> </div>

View File

@ -6,8 +6,48 @@
width: 100%; width: 100%;
height: 50px; height: 50px;
.ucap-video-viewer-icon {
}
.ucap-video-viewer-title {
}
}
.ucap-video-viewer-body {
position: relative;
background-color: white;
width: 100%;
height: 100%;
padding-bottom: 70px;
.ucap-video-viewer-video-icon {
width: 100%;
height: calc(100% - 60px);
}
.ucap-video-viewer-video-time {
width: 100%;
height: 30px;
}
.ucap-video-viewer-video-controls {
width: 100%;
height: 30px;
.ucap-video-viewer-video-time-current {
padding-left: 30px;
}
.ucap-video-viewer-video-time-total {
padding-right: 30px;
}
}
}
.ucap-video-viewer-spacer { .ucap-video-viewer-spacer {
flex: 1 1 auto; flex: 1 1 auto;
} }
.ucap-video-viewer-action {
} }
} }
mat-slider {
width: 95%;
}

View File

@ -1,7 +1,15 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import {
Component,
OnInit,
Input,
Output,
EventEmitter,
ViewChild,
ElementRef
} from '@angular/core';
import { ucapAnimations } from '../../animations'; import { ucapAnimations } from '../../animations';
import { FileEventJson } from '@ucap-webmessenger/protocol-event'; import { FileEventJson } from '@ucap-webmessenger/protocol-event';
import { DeviceType } from '@ucap-webmessenger/core'; import { MatSlider, MatSliderChange } from '@angular/material';
@Component({ @Component({
selector: 'ucap-video-viewer', selector: 'ucap-video-viewer',
@ -22,11 +30,68 @@ export class VideoViewerComponent implements OnInit {
@Output() @Output()
closed = new EventEmitter<void>(); closed = new EventEmitter<void>();
@ViewChild('audioPlayer', { static: true })
audioPlayer: ElementRef<HTMLVideoElement>;
@ViewChild('timeSlider', { static: true })
timeSlider: MatSlider;
playing = false;
duration = 0.01;
currentTime = 0;
volume = 0.1;
loading = false;
constructor() {} constructor() {}
ngOnInit() {} ngOnInit() {}
onClickDownload(): void {} onClickPlayOrPause(): void {
if (this.loading) {
return;
}
if (this.audioPlayer.nativeElement.paused) {
this.audioPlayer.nativeElement.play();
} else {
this.currentTime = this.audioPlayer.nativeElement.currentTime;
this.audioPlayer.nativeElement.pause();
}
}
onChangeTimeSlider(e: MatSliderChange): void {
console.log('onChangeTimeSlider', e.value);
this.audioPlayer.nativeElement.currentTime = e.value;
}
onPlayingVideo(): void {
this.playing = true;
// this.duration = Math.floor(this.audioPlayer.nativeElement.duration);
}
onPauseVideo(): void {
this.playing = false;
}
onTimeUpdateVideo(): void {
this.currentTime = Math.floor(this.audioPlayer.nativeElement.currentTime);
}
onVolumeChangeVideo(): void {
this.volume = Math.floor(this.audioPlayer.nativeElement.volume);
}
onLoadStartVideo(): void {
this.loading = true;
}
onLoadedDataVideo(): void {
this.loading = false;
this.duration = Math.floor(this.audioPlayer.nativeElement.duration);
}
onClickDownload(): void {
this.download.emit();
}
onClickClose(): void { onClickClose(): void {
this.closed.emit(); this.closed.emit();

View File

@ -0,0 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'ucapSecondsToMinutes' })
export class SecondsToMinutesPipe implements PipeTransform {
public transform(time: number): string {
const minutes = ('0' + Math.floor(time / 60)).slice(-2);
const seconds = ('0' + (time % 60)).slice(-2);
return `${minutes}:${seconds}`;
}
}

View File

@ -9,6 +9,7 @@ import { MatCardModule } from '@angular/material/card';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSliderModule } from '@angular/material/slider';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
@ -39,6 +40,7 @@ import { ConfirmDialogComponent } from './dialogs/confirm.dialog.component';
import { BytesPipe } from './pipes/bytes.pipe'; import { BytesPipe } from './pipes/bytes.pipe';
import { LinefeedToHtmlPipe, HtmlToLinefeedPipe } from './pipes/linefeed.pipe'; import { LinefeedToHtmlPipe, HtmlToLinefeedPipe } from './pipes/linefeed.pipe';
import { DateToStringForChatRoomListPipe } from './pipes/dates.pipe'; import { DateToStringForChatRoomListPipe } from './pipes/dates.pipe';
import { SecondsToMinutesPipe } from './pipes/seconds-to-minutes.pipe';
const COMPONENTS = [ const COMPONENTS = [
FileUploadQueueComponent, FileUploadQueueComponent,
@ -61,7 +63,8 @@ const PIPES = [
BytesPipe, BytesPipe,
LinefeedToHtmlPipe, LinefeedToHtmlPipe,
HtmlToLinefeedPipe, HtmlToLinefeedPipe,
DateToStringForChatRoomListPipe DateToStringForChatRoomListPipe,
SecondsToMinutesPipe
]; ];
const SERVICES = [ const SERVICES = [
BottomSheetService, BottomSheetService,
@ -79,6 +82,7 @@ const SERVICES = [
MatDialogModule, MatDialogModule,
MatIconModule, MatIconModule,
MatProgressBarModule, MatProgressBarModule,
MatSliderModule,
MatSnackBarModule, MatSnackBarModule,
MatToolbarModule, MatToolbarModule,
MatTooltipModule, MatTooltipModule,