...
 
Commits (2)
......@@ -471,7 +471,9 @@ export class Activity implements OnInit {
class: 'm-overlayModal--media'
}).present();
} else {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
if (this.activity.custom_type !== 'video'){
this.router.navigate([`/media/${this.activity.entity_guid}`]);
}
}
}
......
......@@ -181,7 +181,9 @@ export class Remind {
class: 'm-overlayModal--media'
}).present();
} else {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
if (this.activity.custom_type !== 'video') {
this.router.navigate([`/media/${this.activity.entity_guid}`]);
}
}
}
}
......@@ -80,10 +80,6 @@ export class MindsVideoTorrentPlayer implements OnInit, AfterViewInit, OnDestroy
this._emitCanPlayThrough();
};
protected _dblClick = () => {
this.requestFullScreen();
};
protected _onError = e => {
this.loading = false;
this.detectChanges();
......@@ -131,7 +127,6 @@ export class MindsVideoTorrentPlayer implements OnInit, AfterViewInit, OnDestroy
ngOnInit() {
const player = this.getPlayer();
player.addEventListener('dblclick', this._dblClick);
player.addEventListener('playing', this._emitPlay);
player.addEventListener('pause', this._emitPause);
player.addEventListener('ended', this._emitEnd);
......@@ -161,7 +156,6 @@ export class MindsVideoTorrentPlayer implements OnInit, AfterViewInit, OnDestroy
const player = this.getPlayer();
if (player) {
player.removeEventListener('dblclick', this._dblClick);
player.removeEventListener('playing', this._emitPlay);
player.removeEventListener('pause', this._emitPause);
player.removeEventListener('ended', this._emitEnd);
......
......@@ -34,7 +34,7 @@
></m-video--torrent-player>
<ng-container *ngIf="playerRef">
<i *ngIf="!playerRef.isPlaying() && !playerRef.isLoading() || isActivity && metadataLoaded"
<i *ngIf="(!playerRef.isPlaying() && !playerRef.isLoading()) || (isActivity && metadataLoaded && !playerRef.isPlaying())"
class="material-icons minds-video-play-icon"
(click)="isModal ? toggle() : requestMediaModal()"
>play_circle_outline</i>
......
......@@ -22,6 +22,10 @@ import { webtorrentServiceMock } from '../../../../../tests/webtorrent-service-m
import { MindsPlayerInterface } from './players/player.interface';
import { MediaModalComponent } from '../../modal/modal.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FeaturesService } from '../../../../services/features.service';
import { featuresServiceMock } from '../../../../../tests/features-service-mock.spec';
@Component({
selector: 'm-video--direct-http-player',
......@@ -174,7 +178,8 @@ describe('MindsVideo', () => {
providers: [
{ provide: ScrollService, useValue: scrollServiceMock },
{ provide: Client, useValue: clientMock },
{ provide: WebtorrentService, useValue: webtorrentServiceMock }
{ provide: WebtorrentService, useValue: webtorrentServiceMock },
{ provide: FeaturesService, useValue: featuresServiceMock },
]
})
.compileComponents(); // compile template and css
......
import { Component, ElementRef, Input, Output, EventEmitter, ViewChild, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { Component, ElementRef, Input, Output, EventEmitter, ViewChild, ChangeDetectorRef, OnDestroy, HostListener } from '@angular/core';
import { Router } from '@angular/router';
import { trigger, state, style, animate, transition } from '@angular/animations';
import { MindsVideoProgressBar } from './progress-bar/progress-bar.component';
......@@ -9,6 +9,7 @@ import { ScrollService } from '../../../../services/ux/scroll';
import { MindsPlayerInterface } from './players/player.interface';
import { WebtorrentService } from '../../../webtorrent/webtorrent.service';
import { SOURCE_CANDIDATE_PICK_ZIGZAG, SourceCandidates } from './source-candidates';
import { FeaturesService } from '../../../../services/features.service';
import isMobile from '../../../../helpers/is-mobile';
@Component({
......@@ -16,7 +17,7 @@ import isMobile from '../../../../helpers/is-mobile';
host: {
'(mouseenter)': 'onMouseEnter()',
'(mouseleave)': 'onMouseLeave()',
'[class.clickable]':'canPlayThrough'
'[class.clickable]':'metadataLoaded'
},
templateUrl: 'video.component.html',
animations: [
......@@ -43,6 +44,8 @@ export class MindsVideoComponent implements OnDestroy {
@Input() poster: string = '';
@Input() isActivity: boolean = false;
@Input() isModal: boolean = false;
// @Input() isTheatre: boolean = false;
@Output('finished') finished: EventEmitter<any> = new EventEmitter();
......@@ -82,6 +85,7 @@ export class MindsVideoComponent implements OnDestroy {
stopSeekerTimeout: any = null;
metadataLoaded: boolean = false;
canPlayThrough: boolean = false;
isFullscreen: boolean = false;
current: { type: 'torrent' | 'direct-http', src: string };
protected candidates: SourceCandidates = new SourceCandidates();
......@@ -100,6 +104,7 @@ export class MindsVideoComponent implements OnDestroy {
public client: Client,
protected webtorrent: WebtorrentService,
protected cd: ChangeDetectorRef,
protected featuresService: FeaturesService,
private router: Router,
) { }
......@@ -185,17 +190,18 @@ export class MindsVideoComponent implements OnDestroy {
}
onMouseEnter() {
if (this.isActivity) {
if (this.isActivity && this.featuresService.has('media-modal')) {
return;
}
this.progressBar.getSeeker();
this.progressBar.enableKeyControls();
this.showControls = true;
if (this.videoMetadataLoaded){
this.progressBar.getSeeker();
this.progressBar.enableKeyControls();
this.showControls = true;
}
}
onMouseLeave() {
if (this.stageHover || this.isActivity) {
if (this.featuresService.has('media-modal') && (this.stageHover || this.isActivity)) {
return;
}
......@@ -392,17 +398,22 @@ export class MindsVideoComponent implements OnDestroy {
}
requestMediaModal() {
if (!this.canPlayThrough) {
if (!this.metadataLoaded) {
return;
}
if (this.isModal) {
let isMediaPage = false;
if (!this.isModal && !this.isActivity) {
isMediaPage = true;
}
if (this.isModal || (!isMediaPage && !this.featuresService.has('media-modal'))) {
this.toggle();
return;
}
// Mobile (not tablet) users go to media page instead of modal
if (isMobile() && Math.min(screen.width, screen.height) < 768) {
if (isMobile() && !isMediaPage && Math.min(screen.width, screen.height) < 768) {
this.router.navigate([`/media/${this.guid}`]);
}
......@@ -413,6 +424,61 @@ export class MindsVideoComponent implements OnDestroy {
this.cd.markForCheck();
this.cd.detectChanges();
}
// * FULLSCREEN * --------------------------------------------------------------------------------
// Listen for fullscreen change event in case user enters/exits full screen without clicking button
@HostListener('document:fullscreenchange', ['$event'])
@HostListener('document:webkitfullscreenchange', ['$event'])
@HostListener('document:mozfullscreenchange', ['$event'])
@HostListener('document:MSFullscreenChange', ['$event'])
onFullscreenChange(event) {
if ( !document.fullscreenElement &&
!document['webkitFullscreenElement'] &&
!document['mozFullScreenElement'] &&
!document['msFullscreenElement'] ) {
this.isFullscreen = false;
} else {
this.isFullscreen = true;
}
}
toggleFullscreen() {
const elem = document.querySelector('m-video');
// this.fullscreenHovering = false;
// If fullscreen is not already enabled
if ( !document['fullscreenElement'] &&
!document['webkitFullscreenElement'] &&
!document['mozFullScreenElement'] &&
!document['msFullscreenElement'] ) {
// Request full screen
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem['webkitRequestFullscreen']) {
elem['webkitRequestFullscreen']();
} else if (elem['mozRequestFullScreen']) {
elem['mozRequestFullScreen']();
} else if (elem['msRequestFullscreen']) {
elem['msRequestFullscreen']();
}
this.isFullscreen = true;
return;
}
// If fullscreen is already enabled, exit it
if ( document.exitFullscreen ) {
document.exitFullscreen();
} else if (document['webkitExitFullscreen']) {
document['webkitExitFullscreen']();
} else if (document['mozCancelFullScreen']) {
document['mozCancelFullScreen']();
} else if (document['msExitFullscreen']) {
document['msExitFullscreen']();
}
this.isFullscreen = false;
}
}
export { VideoAds } from './ads.component';
......@@ -596,3 +596,9 @@ m-media--grid{
.m-media-content--button-boost{
padding-left: $minds-padding;
}
.m-comment__attachment {
img, minds-video {
max-width: 50%;
}
}
......@@ -12,7 +12,7 @@
[style.line-height]="stageHeight + 'px'"
(mouseenter)="onMouseEnterStage()"
(mouseleave)="onMouseLeaveStage()"
(touchend)="showOverlays()"
(touchend)="showOverlaysOnTablet()"
>
<!-- LOADING PANEL -->
<div class="m-mediaModal__loadingPanel" *ngIf="isLoading">
......@@ -31,8 +31,8 @@
<img class="m-mediaModal__media--image"
[src]="thumbnail"
(load)="isLoaded()"
[style.height]="entity.height + 'px'"
[style.width]="entity.width + 'px'"
[style.height]="entityHeight + 'px'"
[style.width]="entityWidth + 'px'"
/>
</div>
......@@ -43,8 +43,8 @@
[style.height]="mediaHeight + 'px'"
>
<m-video class="m-mediaModal__media--video"
[style.height]="entity.height + 'px'"
[style.width]="entity.width + 'px'"
[style.height]="entityHeight + 'px'"
[style.width]="entityWidth + 'px'"
[isModal]="true"
[autoplay]="true"
[muted]="false"
......@@ -62,22 +62,28 @@
<!-- OVERLAY -->
<div class="m-mediaModal__overlayContainer"
*ngIf="showOverlay"
*ngIf="overlayVisible"
@fastFadeAnimation
>
<div class="m-mediaModal__overlayTitleWrapper">
<!-- TITLE -->
<span class="m-mediaModal__overlayTitle m-mediaModal__overlayTitle--notFullscreen" *ngIf="!isFullscreen">
<a [routerLink]="['/media', entity.entity_guid]">{{title}}</a>
<a [routerLink]="['/media', entity.entity_guid]"
(click)="$event.stopPropagation()"
>{{title}}</a>
</span>
<!-- TITLE: FULLSCREEN -->
<span class="m-mediaModal__overlayTitle m-mediaModal__overlayTitle--fullscreen" *ngIf="isFullscreen">
<a [routerLink]="['/', entity.ownerObj.username]">
<a [routerLink]="['/', entity.ownerObj.username]"
(click)="$event.stopPropagation()"
>
<img class="avatar" [src]="minds.cdn_url + 'icon/' + entity.ownerObj.guid + '/small/' + ownerIconTime" class="mdl-shadow--2dp"/>
<span title={{entity.ownerObj.name}}>{{entity.ownerObj.name}}</span>
</a>
<div class="m-mediaModal__overlayTitleSeparator"></div>
<a [routerLink]="['/media', entity.entity_guid]">{{title}}</a>
<a [routerLink]="['/media', entity.entity_guid]"
(click)="$event.stopPropagation()"
>{{title}}</a>
</span>
</div>
<!-- FULLSCREEN BUTTON -->
......
......@@ -55,6 +55,8 @@ export class MediaModalComponent implements OnInit, OnDestroy {
stageHeight: number;
mediaWidth: number;
mediaHeight: number;
entityWidth: number;
entityHeight: number;
maxStageWidth: number;
maxHeight: number;
......@@ -76,15 +78,15 @@ export class MediaModalComponent implements OnInit, OnDestroy {
isOpen: boolean = false;
isOpenTimeout: any = null;
showOverlay: boolean = false;
overlayVisible: boolean = false;
tabletOverlayTimeout: any = null;
routerSubscription: Subscription;
@Input('entity') set data(entity) {
this.entity = entity;
this.entity.width = 0;
this.entity.height = 0;
this.entityWidth = 0;
this.entityHeight = 0;
}
// Used to make sure video progress bar seeker / hover works
......@@ -145,7 +147,6 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// (but don't actually redirect)
this.location.replaceState(`/media/${this.entity.entity_guid}`);
// When user clicks a link from inside the modal
this.routerSubscription = this.router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
......@@ -168,16 +169,16 @@ export class MediaModalComponent implements OnInit, OnDestroy {
if (!this.isVideo) {
// Image
this.entity.width = this.entity.custom_data[0].width;
this.entity.height = this.entity.custom_data[0].height;
this.entityWidth = this.entity.custom_data[0].width;
this.entityHeight = this.entity.custom_data[0].height;
this.thumbnail = `${this.minds.cdn_url}fs/v1/thumbnail/${this.entity.entity_guid}/xlarge`;
} else {
this.entity.width = this.entity.custom_data.dimensions.width;
this.entity.height = this.entity.custom_data.dimensions.height;
this.entityWidth = this.entity.custom_data.dimensions.width;
this.entityHeight = this.entity.custom_data.dimensions.height;
this.thumbnail = this.entity.custom_data.thumbnail_src; // Not currently used
}
this.aspectRatio = this.entity.width / this.entity.height;
this.aspectRatio = this.entityWidth / this.entityHeight;
this.calculateDimensions();
}
......@@ -211,7 +212,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// If black stage background is visible on top/bottom, each strip should be at least 20px high
const heightDiff = this.stageHeight - this.mediaHeight;
if ( 0 < heightDiff && heightDiff <= this.padding * 2) {
this.stageHeight += 40;
this.stageHeight += (this.padding * 2);
}
} else { // isFullscreen
......@@ -221,8 +222,15 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.stageWidth = windowWidth;
this.stageHeight = windowHeight;
// Set mediaHeight as tall as possible but not taller than instrinsic height
this.mediaHeight = this.entity.height < windowHeight ? this.entity.height : windowHeight;
if (this.entity.custom_type === 'image') {
// For images, set mediaHeight as tall as possible but not taller than instrinsic height
this.mediaHeight = this.entityHeight < windowHeight ? this.entityHeight : windowHeight;
} else {
// It's ok if videos are taller than intrinsic height
this.mediaHeight = windowHeight;
}
this.mediaWidth = this.scaleWidth();
if ( this.mediaWidth > windowWidth ) {
......@@ -233,8 +241,8 @@ export class MediaModalComponent implements OnInit, OnDestroy {
}
if (this.isVideo) {
this.entity.height = this.mediaHeight;
this.entity.width = this.mediaWidth;
this.entityHeight = this.mediaHeight;
this.entityWidth = this.mediaWidth;
}
this.modalWidth = this.stageWidth + this.contentWidth;
......@@ -248,13 +256,13 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.stageHeight = Math.max(this.maxHeight, this.minStageHeight);
// Set mediaHeight as tall as stage but no larger than intrinsic height
if (!this.isVideo && this.entity.height < this.stageHeight) {
if (!this.isVideo && this.entityHeight < this.stageHeight) {
// Image is shorter than stage; scale down stage
this.mediaHeight = this.entity.height;
this.mediaHeight = this.entityHeight;
this.stageHeight = Math.max(this.mediaHeight, this.minStageHeight);
} else {
// Image is taller than stage; scale it down so it fits inside stage
// All videos should be as tall as possible but not taller than stage
// Either: Image is taller than stage; scale it down so it fits inside stage
// Or: Video should be as tall as possible but not taller than stage
this.mediaHeight = this.stageHeight;
}
......@@ -276,13 +284,11 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// shrink vertically until it hits minStageHeight
// When window is narrower than this, start to shrink height
const verticalShrinkWidthThreshold = this.mediaWidth + this.contentWidth + (this.padding * 4); // + 2;
const verticalShrinkWidthThreshold = this.mediaWidth + this.contentWidth + (this.padding * 4);
const widthDiff = verticalShrinkWidthThreshold - window.innerWidth;
// Is window narrow enough to start shrinking vertically?
if ( widthDiff >= 1 ) {
if (widthDiff >= 1) {
// What mediaHeight would be if it shrunk proportionally to difference in width
const mediaHeightPreview = Math.round((this.mediaWidth - widthDiff) / this.aspectRatio);
......@@ -293,7 +299,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.stageHeight = this.mediaHeight;
} else {
this.stageHeight = this.minStageHeight;
this.mediaHeight = Math.min(this.minStageHeight, this.entity.height);
this.mediaHeight = Math.min(this.minStageHeight, this.entityHeight);
this.mediaWidth = this.scaleWidth();
}
}
......@@ -316,7 +322,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
onFullscreenChange(event) {
this.calculateDimensions();
if ( !document.fullscreenElement &&
!document['webkitFullScreenElement'] &&
!document['webkitFullscreenElement'] &&
!document['mozFullScreenElement'] &&
!document['msFullscreenElement'] ) {
this.isFullscreen = false;
......@@ -332,10 +338,10 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// If fullscreen is not already enabled
if ( !document['fullscreenElement'] &&
!document['webkitFullScreenElement'] &&
!document['webkitFullscreenElement'] &&
!document['mozFullScreenElement'] &&
!document['msFullscreenElement'] ) {
// Request full screen
// Request full screen
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem['webkitRequestFullscreen']) {
......@@ -384,7 +390,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// Show overlay and video controls
onMouseEnterStage() {
this.showOverlay = true;
this.overlayVisible = true;
if (this.isVideo) {
// Make sure progress bar seeker is updating when video controls are visible
......@@ -393,9 +399,8 @@ export class MediaModalComponent implements OnInit, OnDestroy {
}
}
// Hide overlay and video controls
onMouseLeaveStage() {
this.showOverlay = false;
this.overlayVisible = false;
if (this.isVideo) {
// Stop updating progress bar seeker when controls aren't visible
......@@ -407,7 +412,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
// * TABLETS ONLY: SHOW OVERLAY & VIDEO CONTROLS * -------------------------------------------
// Briefly display title overlay and video controls when finished loading and stage touch
showOverlays() {
showOverlaysOnTablet() {
this.onMouseEnterStage();
if (this.tabletOverlayTimeout) {
......@@ -425,7 +430,7 @@ export class MediaModalComponent implements OnInit, OnDestroy {
this.isLoading = false;
if ( this.isTablet ) {
this.showOverlays();
this.showOverlaysOnTablet();
}
}
......
......@@ -269,7 +269,7 @@
<ng-template ngSwitchCase="remind">
<div *ngIf="notification.entityObj">
<a [routerLink]="['/newsfeed', notification.entityObj.guid]" *ngIf="notification.entityObj.type == 'object'">
<p><ng-container i18n="@@NOTIFICATIONS__NOTIFICATION__REMIND__REMINDED">{{notification.fromObj.name}} reminded</ng-container>
<p><ng-container i18n="@@NOTIFICATIONS__NOTIFICATION__REMIND__REMINDED">{{notification.fromObj.name}} reminded </ng-container>
<span class="pseudo-link mdl-color-text--blue-grey-400" *ngIf="notification.entityObj.title">{{notification.entityObj.title | excerpt}}</span>
<span class="pseudo-link mdl-color-text--blue-grey-400" *ngIf="!notification.entityObj.title" i18n="object belonging to current user@@NOTIFICATIONS__NOTIFICATION__OWN_OBJECT">your {{notification.entityObj.subtype}}</span>
</p>
......