From 941c5eac1751ef93500d6afa58c4575f777dbff9 Mon Sep 17 00:00:00 2001
From: Chocobozzz <me@florianbigard.com>
Date: Fri, 6 Dec 2019 09:55:36 +0100
Subject: [PATCH] Add missing hotkeys to the watch page

---
 .../+video-watch/video-watch.component.ts     | 58 ++++++++++++-------
 server/models/activitypub/actor.ts            | 17 +++---
 server/tests/api/videos/video-transcoder.ts   |  4 +-
 server/tests/cli/optimize-old-videos.ts       |  4 +-
 4 files changed, 50 insertions(+), 33 deletions(-)

diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index adf6dc12f..eee7adfd8 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -551,26 +551,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     }
   }
 
-  private initHotkeys () {
-    this.hotkeys = [
-      new Hotkey('shift+l', () => {
-        this.setLike()
-        return false
-      }, undefined, this.i18n('Like the video')),
-
-      new Hotkey('shift+d', () => {
-        this.setDislike()
-        return false
-      }, undefined, this.i18n('Dislike the video')),
-
-      new Hotkey('shift+s', () => {
-        this.subscribeButton.subscribed ? this.subscribeButton.unsubscribe() : this.subscribeButton.subscribe()
-        return false
-      }, undefined, this.i18n('Subscribe to the account'))
-    ]
-    if (this.isUserLoggedIn()) this.hotkeysService.add(this.hotkeys)
-  }
-
   private buildPlayerManagerOptions (params: {
     video: VideoDetails,
     videoCaptions: VideoCaption[],
@@ -667,4 +647,42 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
     this.player.pause()
   }
+
+  private initHotkeys () {
+    this.hotkeys = [
+      new Hotkey('shift+l', () => {
+        this.setLike()
+        return false
+      }, undefined, this.i18n('Like the video')),
+
+      new Hotkey('shift+d', () => {
+        this.setDislike()
+        return false
+      }, undefined, this.i18n('Dislike the video')),
+
+      new Hotkey('shift+s', () => {
+        this.subscribeButton.subscribed ? this.subscribeButton.unsubscribe() : this.subscribeButton.subscribe()
+        return false
+      }, undefined, this.i18n('Subscribe to the account')),
+
+      // These hotkeys are managed by the player
+      new Hotkey('f', e => e, undefined, this.i18n('Enter/exit fullscreen (requires player focus)')),
+      new Hotkey('space', e => e, undefined, this.i18n('Play/Pause the video (requires player focus)')),
+      new Hotkey('m', e => e, undefined, this.i18n('Mute/unmute the video (requires player focus)')),
+
+      new Hotkey('0-9', e => e, undefined, this.i18n('Skip to a percentage of the video: 0 is 0% and 9 is 90% (requires player focus)')),
+
+      new Hotkey('up', e => e, undefined, this.i18n('Increase the volume (requires player focus)')),
+      new Hotkey('down', e => e, undefined, this.i18n('Decrease the volume (requires player focus)')),
+
+      new Hotkey('right', e => e, undefined, this.i18n('Seek the video forward (requires player focus)')),
+      new Hotkey('left', e => e, undefined, this.i18n('Seek the video backward (requires player focus)')),
+
+      new Hotkey('>', e => e, undefined, this.i18n('Increase playback rate (requires player focus)')),
+      new Hotkey('<', e => e, undefined, this.i18n('Decrease playback rate (requires player focus)')),
+
+      new Hotkey('.', e => e, undefined, this.i18n('Navigate in the video frame by frame (requires player focus)'))
+    ]
+    if (this.isUserLoggedIn()) this.hotkeysService.add(this.hotkeys)
+  }
 }
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 66a13b857..e539b579c 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -1,6 +1,5 @@
 import { values } from 'lodash'
 import { extname } from 'path'
-import * as Sequelize from 'sequelize'
 import {
   AllowNull,
   BelongsTo,
@@ -48,7 +47,7 @@ import {
   MActorWithInboxes
 } from '../../typings/models'
 import * as Bluebird from 'bluebird'
-import { Op } from 'sequelize'
+import { Op, Transaction } from 'sequelize'
 
 enum ScopeNames {
   FULL = 'FULL'
@@ -285,7 +284,7 @@ export class ActorModel extends Model<ActorModel> {
     return ActorModel.scope(ScopeNames.FULL).findByPk(id)
   }
 
-  static loadFromAccountByVideoId (videoId: number, transaction: Sequelize.Transaction): Bluebird<MActor> {
+  static loadFromAccountByVideoId (videoId: number, transaction: Transaction): Bluebird<MActor> {
     const query = {
       include: [
         {
@@ -329,11 +328,11 @@ export class ActorModel extends Model<ActorModel> {
       .then(a => !!a)
   }
 
-  static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction): Bluebird<MActorFull[]> {
+  static listByFollowersUrls (followersUrls: string[], transaction?: Transaction): Bluebird<MActorFull[]> {
     const query = {
       where: {
         followersUrl: {
-          [ Sequelize.Op.in ]: followersUrls
+          [ Op.in ]: followersUrls
         }
       },
       transaction
@@ -342,7 +341,7 @@ export class ActorModel extends Model<ActorModel> {
     return ActorModel.scope(ScopeNames.FULL).findAll(query)
   }
 
-  static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> {
+  static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> {
     const query = {
       where: {
         preferredUsername,
@@ -373,7 +372,7 @@ export class ActorModel extends Model<ActorModel> {
     return ActorModel.scope(ScopeNames.FULL).findOne(query)
   }
 
-  static loadByUrl (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorAccountChannelId> {
+  static loadByUrl (url: string, transaction?: Transaction): Bluebird<MActorAccountChannelId> {
     const query = {
       where: {
         url
@@ -396,7 +395,7 @@ export class ActorModel extends Model<ActorModel> {
     return ActorModel.unscoped().findOne(query)
   }
 
-  static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> {
+  static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Transaction): Bluebird<MActorFull> {
     const query = {
       where: {
         url
@@ -483,7 +482,7 @@ export class ActorModel extends Model<ActorModel> {
     return activityPubContextify(json)
   }
 
-  getFollowerSharedInboxUrls (t: Sequelize.Transaction) {
+  getFollowerSharedInboxUrls (t: Transaction) {
     const query = {
       attributes: [ 'sharedInboxUrl' ],
       include: [
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts
index 90ade1652..4be74901a 100644
--- a/server/tests/api/videos/video-transcoder.ts
+++ b/server/tests/api/videos/video-transcoder.ts
@@ -292,7 +292,7 @@ describe('Test video transcoding', function () {
       tempFixturePath = await generateHighBitrateVideo()
 
       const bitrate = await getVideoFileBitrate(tempFixturePath)
-      expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
+      expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
     }
 
     const videoAttributes = {
@@ -331,7 +331,7 @@ describe('Test video transcoding', function () {
       tempFixturePath = await generateHighBitrateVideo()
 
       const bitrate = await getVideoFileBitrate(tempFixturePath)
-      expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
+      expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
     }
 
     for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
diff --git a/server/tests/cli/optimize-old-videos.ts b/server/tests/cli/optimize-old-videos.ts
index fa82f962c..de5d672f5 100644
--- a/server/tests/cli/optimize-old-videos.ts
+++ b/server/tests/cli/optimize-old-videos.ts
@@ -46,7 +46,7 @@ describe('Test optimize old videos', function () {
       tempFixturePath = await generateHighBitrateVideo()
 
       const bitrate = await getVideoFileBitrate(tempFixturePath)
-      expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
+      expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
     }
 
     // Upload two videos for our needs
@@ -102,7 +102,7 @@ describe('Test optimize old videos', function () {
         expect(videosDetails.files).to.have.lengthOf(1)
         const file = videosDetails.files[0]
 
-        expect(file.size).to.be.below(5000000)
+        expect(file.size).to.be.below(8000000)
 
         const path = join(root(), 'test' + servers[0].internalServerNumber, 'videos', video.uuid + '-' + file.resolution.id + '.mp4')
         const bitrate = await getVideoFileBitrate(path)