diff options
5 files changed, 236 insertions, 177 deletions
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index 3df5b7b19..91f77cbf3 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -1,4 +1,4 @@ | |||
1 | <div class="root-row row"> | 1 | <div class="root" [ngClass]="{ 'theater-enabled': theaterEnabled }"> |
2 | <!-- We need the video container for videojs so we just hide it --> | 2 | <!-- We need the video container for videojs so we just hide it --> |
3 | <div id="video-wrapper"> | 3 | <div id="video-wrapper"> |
4 | <div *ngIf="remoteServerDown" class="remote-server-down"> | 4 | <div *ngIf="remoteServerDown" class="remote-server-down"> |
@@ -59,200 +59,198 @@ | |||
59 | 59 | ||
60 | <!-- Video information --> | 60 | <!-- Video information --> |
61 | <div *ngIf="video" class="margin-content video-bottom"> | 61 | <div *ngIf="video" class="margin-content video-bottom"> |
62 | <div class="row fullWidth"> | 62 | <div class="video-info"> |
63 | <div class="col-12 col-lg-auto video-info"> | 63 | <div class="video-info-first-row"> |
64 | <div class="video-info-first-row"> | 64 | <div> |
65 | <div> | 65 | <div class="d-block d-md-none"> <!-- only shown on medium devices, has its conterpart for larger viewports below --> |
66 | <div class="d-block d-sm-none"> <!-- only shown on small devices, has its conterpart for larger viewports below --> | 66 | <h1 class="video-info-name">{{ video.name }}</h1> |
67 | <div i18n class="video-info-date-views"> | ||
68 | Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views | ||
69 | </div> | ||
70 | </div> | ||
71 | |||
72 | <div class="d-flex justify-content-between align-items-md-end"> | ||
73 | <div class="d-none d-md-block"> | ||
67 | <h1 class="video-info-name">{{ video.name }}</h1> | 74 | <h1 class="video-info-name">{{ video.name }}</h1> |
75 | |||
68 | <div i18n class="video-info-date-views"> | 76 | <div i18n class="video-info-date-views"> |
69 | Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views | 77 | Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views |
70 | </div> | 78 | </div> |
71 | </div> | 79 | </div> |
72 | 80 | ||
73 | <div class="d-flex justify-content-between align-items-sm-end"> | 81 | <div class="video-actions-rates"> |
74 | <div class="d-none d-sm-block"> | 82 | <div class="video-actions fullWidth justify-content-end"> |
75 | <h1 class="video-info-name">{{ video.name }}</h1> | 83 | <div |
84 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" | ||
85 | class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'" | ||
86 | i18n-title title="Like this video" | ||
87 | > | ||
88 | <my-global-icon iconName="like"></my-global-icon> | ||
89 | </div> | ||
76 | 90 | ||
77 | <div i18n class="video-info-date-views"> | 91 | <div |
78 | Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views | 92 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" |
93 | class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" | ||
94 | i18n-title title="Dislike this video" | ||
95 | > | ||
96 | <my-global-icon iconName="dislike"></my-global-icon> | ||
79 | </div> | 97 | </div> |
80 | </div> | ||
81 | 98 | ||
82 | <div class="video-actions-rates"> | 99 | <div *ngIf="video.support" (click)="showSupportModal()" class="action-button"> |
83 | <div class="video-actions fullWidth justify-content-end"> | 100 | <my-global-icon iconName="heart"></my-global-icon> |
84 | <div | 101 | <span class="icon-text" i18n>Support</span> |
85 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" | 102 | </div> |
86 | class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'" | ||
87 | i18n-title title="Like this video" | ||
88 | > | ||
89 | <my-global-icon iconName="like"></my-global-icon> | ||
90 | </div> | ||
91 | 103 | ||
92 | <div | 104 | <div (click)="showShareModal()" class="action-button" role="button"> |
93 | *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" | 105 | <my-global-icon iconName="share"></my-global-icon> |
94 | class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'" | 106 | <span class="icon-text" i18n>Share</span> |
95 | i18n-title title="Dislike this video" | 107 | </div> |
96 | > | ||
97 | <my-global-icon iconName="dislike"></my-global-icon> | ||
98 | </div> | ||
99 | 108 | ||
100 | <div *ngIf="video.support" (click)="showSupportModal()" class="action-button"> | 109 | <div |
101 | <my-global-icon iconName="heart"></my-global-icon> | 110 | class="action-dropdown" ngbDropdown placement="top" role="button" autoClose="outside" |
102 | <span class="icon-text" i18n>Support</span> | 111 | *ngIf="isUserLoggedIn()" (openChange)="addContent.openChange($event)" |
112 | > | ||
113 | <div class="action-button action-button-save" ngbDropdownToggle role="button"> | ||
114 | <my-global-icon iconName="playlist-add"></my-global-icon> | ||
115 | <span class="icon-text" i18n>Save</span> | ||
103 | </div> | 116 | </div> |
104 | 117 | ||
105 | <div (click)="showShareModal()" class="action-button" role="button"> | 118 | <div ngbDropdownMenu> |
106 | <my-global-icon iconName="share"></my-global-icon> | 119 | <my-video-add-to-playlist #addContent [video]="video"></my-video-add-to-playlist> |
107 | <span class="icon-text" i18n>Share</span> | ||
108 | </div> | 120 | </div> |
121 | </div> | ||
109 | 122 | ||
110 | <div | 123 | <div class="action-dropdown" ngbDropdown placement="top" role="button"> |
111 | class="action-dropdown" ngbDropdown placement="top" role="button" autoClose="outside" | 124 | <div class="action-button" ngbDropdownToggle role="button"> |
112 | *ngIf="isUserLoggedIn()" (openChange)="addContent.openChange($event)" | 125 | <my-global-icon class="more-icon" iconName="more-horizontal"></my-global-icon> |
113 | > | ||
114 | <div class="action-button action-button-save" ngbDropdownToggle role="button"> | ||
115 | <my-global-icon iconName="playlist-add"></my-global-icon> | ||
116 | <span class="icon-text" i18n>Save</span> | ||
117 | </div> | ||
118 | |||
119 | <div ngbDropdownMenu> | ||
120 | <my-video-add-to-playlist #addContent [video]="video"></my-video-add-to-playlist> | ||
121 | </div> | ||
122 | </div> | 126 | </div> |
123 | 127 | ||
124 | <div class="action-dropdown" ngbDropdown placement="top" role="button"> | 128 | <div ngbDropdownMenu> |
125 | <div class="action-button" ngbDropdownToggle role="button"> | 129 | <a *ngIf="isVideoDownloadable()" class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> |
126 | <my-global-icon class="more-icon" iconName="more-horizontal"></my-global-icon> | 130 | <my-global-icon iconName="download"></my-global-icon> <ng-container i18n>Download</ng-container> |
127 | </div> | 131 | </a> |
128 | 132 | ||
129 | <div ngbDropdownMenu> | 133 | <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> |
130 | <a *ngIf="isVideoDownloadable()" class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)"> | 134 | <my-global-icon iconName="alert"></my-global-icon> <ng-container i18n>Report</ng-container> |
131 | <my-global-icon iconName="download"></my-global-icon> <ng-container i18n>Download</ng-container> | 135 | </a> |
132 | </a> | ||
133 | 136 | ||
134 | <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)"> | 137 | <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> |
135 | <my-global-icon iconName="alert"></my-global-icon> <ng-container i18n>Report</ng-container> | 138 | <my-global-icon iconName="edit"></my-global-icon> <ng-container i18n>Update</ng-container> |
136 | </a> | 139 | </a> |
137 | 140 | ||
138 | <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> | 141 | <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> |
139 | <my-global-icon iconName="edit"></my-global-icon> <ng-container i18n>Update</ng-container> | 142 | <my-global-icon iconName="no"></my-global-icon> <ng-container i18n>Blacklist</ng-container> |
140 | </a> | 143 | </a> |
141 | 144 | ||
142 | <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)"> | 145 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> |
143 | <my-global-icon iconName="no"></my-global-icon> <ng-container i18n>Blacklist</ng-container> | 146 | <my-global-icon iconName="undo"></my-global-icon> <ng-container i18n>Unblacklist</ng-container> |
144 | </a> | 147 | </a> |
145 | 148 | ||
146 | <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)"> | 149 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> |
147 | <my-global-icon iconName="undo"></my-global-icon> <ng-container i18n>Unblacklist</ng-container> | 150 | <my-global-icon iconName="delete"></my-global-icon> <ng-container i18n>Delete</ng-container> |
148 | </a> | 151 | </a> |
149 | |||
150 | <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)"> | ||
151 | <my-global-icon iconName="delete"></my-global-icon> <ng-container i18n>Delete</ng-container> | ||
152 | </a> | ||
153 | </div> | ||
154 | </div> | 152 | </div> |
155 | </div> | 153 | </div> |
154 | </div> | ||
156 | 155 | ||
157 | <div | 156 | <div |
158 | class="video-info-likes-dislikes-bar" | 157 | class="video-info-likes-dislikes-bar" |
159 | *ngIf="video.likes !== 0 || video.dislikes !== 0" | 158 | *ngIf="video.likes !== 0 || video.dislikes !== 0" |
160 | [ngbTooltip]="likesBarTooltipText" | 159 | [ngbTooltip]="likesBarTooltipText" |
161 | placement="bottom" | 160 | placement="bottom" |
162 | > | 161 | > |
163 | <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div> | 162 | <div class="likes-bar" [ngStyle]="{ 'width.%': video.likesPercent }"></div> |
164 | </div> | ||
165 | </div> | 163 | </div> |
166 | </div> | 164 | </div> |
165 | </div> | ||
167 | 166 | ||
168 | 167 | ||
169 | <div class="pt-3 border-top video-info-channel"> | 168 | <div class="pt-3 border-top video-info-channel"> |
170 | <a [routerLink]="[ '/video-channels', video.byVideoChannel ]" i18n-title title="Go the channel page"> | 169 | <a [routerLink]="[ '/video-channels', video.byVideoChannel ]" i18n-title title="Go the channel page"> |
171 | {{ video.channel.displayName }} | 170 | {{ video.channel.displayName }} |
172 | |||
173 | <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" /> | ||
174 | </a> | ||
175 | 171 | ||
176 | <my-subscribe-button #subscribeButton [videoChannel]="video.channel" size="small"></my-subscribe-button> | 172 | <img [src]="video.videoChannelAvatarUrl" alt="Video channel avatar" /> |
177 | </div> | 173 | </a> |
178 | 174 | ||
179 | <div class="video-info-by"> | 175 | <my-subscribe-button #subscribeButton [videoChannel]="video.channel" size="small"></my-subscribe-button> |
180 | <a [routerLink]="[ '/accounts', video.byAccount ]" i18n-title title="Go to the account page"> | ||
181 | <span i18n>By {{ video.byAccount }}</span> | ||
182 | <img [src]="video.accountAvatarUrl" alt="Account avatar" /> | ||
183 | </a> | ||
184 | </div> | ||
185 | </div> | 176 | </div> |
186 | 177 | ||
178 | <div class="video-info-by"> | ||
179 | <a [routerLink]="[ '/accounts', video.byAccount ]" i18n-title title="Go to the account page"> | ||
180 | <span i18n>By {{ video.byAccount }}</span> | ||
181 | <img [src]="video.accountAvatarUrl" alt="Account avatar" /> | ||
182 | </a> | ||
183 | </div> | ||
187 | </div> | 184 | </div> |
188 | 185 | ||
189 | <div class="video-info-description"> | 186 | </div> |
190 | <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div> | ||
191 | 187 | ||
192 | <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()"> | 188 | <div class="video-info-description"> |
193 | <ng-container i18n>Show more</ng-container> | 189 | <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div> |
194 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span> | ||
195 | <my-small-loader class="description-loading" [loading]="descriptionLoading"></my-small-loader> | ||
196 | </div> | ||
197 | 190 | ||
198 | <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> | 191 | <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()"> |
199 | <ng-container i18n>Show less</ng-container> | 192 | <ng-container i18n>Show more</ng-container> |
200 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span> | 193 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span> |
201 | </div> | 194 | <my-small-loader class="description-loading" [loading]="descriptionLoading"></my-small-loader> |
202 | </div> | 195 | </div> |
203 | 196 | ||
204 | <div class="video-attributes"> | 197 | <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> |
205 | <div class="video-attribute"> | 198 | <ng-container i18n>Show less</ng-container> |
206 | <span i18n class="video-attribute-label">Privacy</span> | 199 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span> |
207 | <span class="video-attribute-value">{{ video.privacy.label }}</span> | 200 | </div> |
208 | </div> | 201 | </div> |
209 | 202 | ||
210 | <div *ngIf="!!video.originallyPublishedAt" class="video-attribute"> | 203 | <div class="video-attributes"> |
211 | <span i18n class="video-attribute-label">Originally published</span> | 204 | <div class="video-attribute"> |
212 | <span class="video-attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span> | 205 | <span i18n class="video-attribute-label">Privacy</span> |
213 | </div> | 206 | <span class="video-attribute-value">{{ video.privacy.label }}</span> |
207 | </div> | ||
214 | 208 | ||
215 | <div class="video-attribute"> | 209 | <div *ngIf="!!video.originallyPublishedAt" class="video-attribute"> |
216 | <span i18n class="video-attribute-label">Category</span> | 210 | <span i18n class="video-attribute-label">Originally published</span> |
217 | <span *ngIf="!video.category.id" class="video-attribute-value">{{ video.category.label }}</span> | 211 | <span class="video-attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span> |
218 | <a | 212 | </div> |
219 | *ngIf="video.category.id" class="video-attribute-value" | ||
220 | [routerLink]="[ '/search' ]" [queryParams]="{ categoryOneOf: [ video.category.id ] }" | ||
221 | >{{ video.category.label }}</a> | ||
222 | </div> | ||
223 | 213 | ||
224 | <div class="video-attribute"> | 214 | <div class="video-attribute"> |
225 | <span i18n class="video-attribute-label">Licence</span> | 215 | <span i18n class="video-attribute-label">Category</span> |
226 | <span *ngIf="!video.licence.id" class="video-attribute-value">{{ video.licence.label }}</span> | 216 | <span *ngIf="!video.category.id" class="video-attribute-value">{{ video.category.label }}</span> |
227 | <a | 217 | <a |
228 | *ngIf="video.licence.id" class="video-attribute-value" | 218 | *ngIf="video.category.id" class="video-attribute-value" |
229 | [routerLink]="[ '/search' ]" [queryParams]="{ licenceOneOf: [ video.licence.id ] }" | 219 | [routerLink]="[ '/search' ]" [queryParams]="{ categoryOneOf: [ video.category.id ] }" |
230 | >{{ video.licence.label }}</a> | 220 | >{{ video.category.label }}</a> |
231 | </div> | 221 | </div> |
232 | 222 | ||
233 | <div class="video-attribute"> | 223 | <div class="video-attribute"> |
234 | <span i18n class="video-attribute-label">Language</span> | 224 | <span i18n class="video-attribute-label">Licence</span> |
235 | <span *ngIf="!video.language.id" class="video-attribute-value">{{ video.language.label }}</span> | 225 | <span *ngIf="!video.licence.id" class="video-attribute-value">{{ video.licence.label }}</span> |
236 | <a | 226 | <a |
237 | *ngIf="video.language.id" class="video-attribute-value" | 227 | *ngIf="video.licence.id" class="video-attribute-value" |
238 | [routerLink]="[ '/search' ]" [queryParams]="{ languageOneOf: [ video.language.id ] }" | 228 | [routerLink]="[ '/search' ]" [queryParams]="{ licenceOneOf: [ video.licence.id ] }" |
239 | >{{ video.language.label }}</a> | 229 | >{{ video.licence.label }}</a> |
240 | </div> | 230 | </div> |
241 | 231 | ||
242 | <div class="video-attribute video-attribute-tags"> | 232 | <div class="video-attribute"> |
243 | <span i18n class="video-attribute-label">Tags</span> | 233 | <span i18n class="video-attribute-label">Language</span> |
244 | <a | 234 | <span *ngIf="!video.language.id" class="video-attribute-value">{{ video.language.label }}</span> |
245 | *ngFor="let tag of getVideoTags()" | 235 | <a |
246 | class="video-attribute-value" [routerLink]="[ '/search' ]" [queryParams]="{ tagsOneOf: [ tag ] }" | 236 | *ngIf="video.language.id" class="video-attribute-value" |
247 | >{{ tag }}</a> | 237 | [routerLink]="[ '/search' ]" [queryParams]="{ languageOneOf: [ video.language.id ] }" |
248 | </div> | 238 | >{{ video.language.label }}</a> |
249 | </div> | 239 | </div> |
250 | 240 | ||
251 | <my-video-comments [video]="video" [user]="user"></my-video-comments> | 241 | <div class="video-attribute video-attribute-tags"> |
242 | <span i18n class="video-attribute-label">Tags</span> | ||
243 | <a | ||
244 | *ngFor="let tag of getVideoTags()" | ||
245 | class="video-attribute-value" [routerLink]="[ '/search' ]" [queryParams]="{ tagsOneOf: [ tag ] }" | ||
246 | >{{ tag }}</a> | ||
247 | </div> | ||
252 | </div> | 248 | </div> |
253 | 249 | ||
254 | <my-recommended-videos [inputRecommendation]="{ uuid: video.uuid, tags: video.tags }" [user]="user"></my-recommended-videos> | 250 | <my-video-comments [video]="video" [user]="user"></my-video-comments> |
255 | </div> | 251 | </div> |
252 | |||
253 | <my-recommended-videos [inputRecommendation]="{ uuid: video.uuid, tags: video.tags }" [user]="user"></my-recommended-videos> | ||
256 | </div> | 254 | </div> |
257 | 255 | ||
258 | <div class="privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> | 256 | <div class="privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss index 281b9240b..11406e887 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.scss +++ b/client/src/app/videos/+video-watch/video-watch.component.scss | |||
@@ -4,9 +4,43 @@ | |||
4 | @import '_miniature'; | 4 | @import '_miniature'; |
5 | 5 | ||
6 | $other-videos-width: 260px; | 6 | $other-videos-width: 260px; |
7 | $player-factor: 1.7; // 16/9 | ||
7 | 8 | ||
8 | .root-row { | 9 | @function getPlayerHeight($width){ |
9 | flex-direction: column; | 10 | @return calc(#{$width} / #{$player-factor}) |
11 | } | ||
12 | |||
13 | @function getPlayerWidth($height){ | ||
14 | @return calc(#{$height} * #{$player-factor}) | ||
15 | } | ||
16 | |||
17 | @mixin playlist-below-player { | ||
18 | width: 100%; | ||
19 | border-bottom: 1px solid $separator-border-color; | ||
20 | } | ||
21 | |||
22 | .root { | ||
23 | margin: 0 -15px; | ||
24 | |||
25 | &.theater-enabled #video-wrapper { | ||
26 | flex-direction: column; | ||
27 | justify-content: center; | ||
28 | |||
29 | #videojs-wrapper { | ||
30 | width: 100%; | ||
31 | } | ||
32 | |||
33 | /deep/ .video-js { | ||
34 | $height: calc(100vh - #{$header-height} - #{$theater-bottom-space}); | ||
35 | |||
36 | height: $height; | ||
37 | width: 100%; | ||
38 | } | ||
39 | |||
40 | .playlist { | ||
41 | @include playlist-below-player; | ||
42 | } | ||
43 | } | ||
10 | } | 44 | } |
11 | 45 | ||
12 | .blacklisted-label { | 46 | .blacklisted-label { |
@@ -17,7 +51,6 @@ $other-videos-width: 260px; | |||
17 | background-color: #000; | 51 | background-color: #000; |
18 | display: flex; | 52 | display: flex; |
19 | justify-content: center; | 53 | justify-content: center; |
20 | flex-grow: 1; | ||
21 | 54 | ||
22 | .remote-server-down { | 55 | .remote-server-down { |
23 | color: #fff; | 56 | color: #fff; |
@@ -93,14 +126,9 @@ $other-videos-width: 260px; | |||
93 | } | 126 | } |
94 | 127 | ||
95 | /deep/ .video-js { | 128 | /deep/ .video-js { |
96 | width: calc(66vh * 1.77); | 129 | width: getPlayerWidth(66vh); |
97 | height: 66vh; | 130 | height: 66vh; |
98 | 131 | ||
99 | &.vjs-theater-enabled { | ||
100 | height: calc(100vh - #{$header-height} - #{$theater-bottom-space}); | ||
101 | width: 100%; | ||
102 | } | ||
103 | |||
104 | // VideoJS create an inner video player | 132 | // VideoJS create an inner video player |
105 | video { | 133 | video { |
106 | outline: 0; | 134 | outline: 0; |
@@ -112,7 +140,7 @@ $other-videos-width: 260px; | |||
112 | .remote-server-down, | 140 | .remote-server-down, |
113 | /deep/ .video-js { | 141 | /deep/ .video-js { |
114 | width: 100vw; | 142 | width: 100vw; |
115 | height: calc(100vw / 1.7); // 16/9 | 143 | height: getPlayerHeight(100vw) |
116 | } | 144 | } |
117 | } | 145 | } |
118 | } | 146 | } |
@@ -131,6 +159,7 @@ $other-videos-width: 260px; | |||
131 | } | 159 | } |
132 | 160 | ||
133 | .video-bottom { | 161 | .video-bottom { |
162 | display: flex; | ||
134 | margin-top: 40px; | 163 | margin-top: 40px; |
135 | 164 | ||
136 | .video-info { | 165 | .video-info { |
@@ -366,7 +395,7 @@ $other-videos-width: 260px; | |||
366 | 395 | ||
367 | /deep/ .other-videos { | 396 | /deep/ .other-videos { |
368 | padding-left: 15px; | 397 | padding-left: 15px; |
369 | width: $other-videos-width; | 398 | flex-basis: $other-videos-width; |
370 | 399 | ||
371 | .title-page { | 400 | .title-page { |
372 | margin-top: 0 !important; | 401 | margin-top: 0 !important; |
@@ -374,14 +403,11 @@ $other-videos-width: 260px; | |||
374 | 403 | ||
375 | .video-miniature { | 404 | .video-miniature { |
376 | display: flex; | 405 | display: flex; |
406 | width: $other-videos-width; | ||
377 | height: 100%; | 407 | height: 100%; |
378 | margin-bottom: 20px; | 408 | margin-bottom: 20px; |
379 | flex-wrap: wrap; | 409 | flex-wrap: wrap; |
380 | 410 | ||
381 | .video-miniature-information { | ||
382 | flex-grow: 1; | ||
383 | } | ||
384 | |||
385 | .video-thumbnail { | 411 | .video-thumbnail { |
386 | margin-right: 10px | 412 | margin-right: 10px |
387 | } | 413 | } |
@@ -455,12 +481,6 @@ my-video-comments { | |||
455 | } | 481 | } |
456 | } | 482 | } |
457 | 483 | ||
458 | @media screen and (min-width: map-get($grid-breakpoints, xl)) { | ||
459 | .video-bottom .video-info { | ||
460 | max-width: calc(100% - #{$other-videos-width}); | ||
461 | } | ||
462 | } | ||
463 | |||
464 | @media screen and (max-width: 1600px) { | 484 | @media screen and (max-width: 1600px) { |
465 | .video-bottom .video-info .video-attributes .video-attribute { | 485 | .video-bottom .video-info .video-attributes .video-attribute { |
466 | margin-bottom: 5px; | 486 | margin-bottom: 5px; |
@@ -478,6 +498,37 @@ my-video-comments { | |||
478 | } | 498 | } |
479 | } | 499 | } |
480 | 500 | ||
501 | @media screen and (max-width: 1100px) { | ||
502 | .video-bottom { | ||
503 | flex-direction: column; | ||
504 | |||
505 | /deep/ .other-videos { | ||
506 | padding-left: 0 !important; | ||
507 | |||
508 | /deep/ .video-miniature { | ||
509 | flex-direction: row; | ||
510 | width: auto; | ||
511 | } | ||
512 | } | ||
513 | } | ||
514 | } | ||
515 | |||
516 | @media screen and (max-width: 900px) { | ||
517 | #video-wrapper { | ||
518 | flex-direction: column; | ||
519 | justify-content: center; | ||
520 | |||
521 | #videojs-wrapper { | ||
522 | display: flex; | ||
523 | justify-content: center; | ||
524 | } | ||
525 | |||
526 | .playlist { | ||
527 | @include playlist-below-player; | ||
528 | } | ||
529 | } | ||
530 | } | ||
531 | |||
481 | @media screen and (max-width: 600px) { | 532 | @media screen and (max-width: 600px) { |
482 | .video-bottom { | 533 | .video-bottom { |
483 | margin: 20px 0 0 0; | 534 | margin: 20px 0 0 0; |
@@ -495,12 +546,8 @@ my-video-comments { | |||
495 | } | 546 | } |
496 | } | 547 | } |
497 | 548 | ||
498 | /deep/ .other-videos { | 549 | /deep/ .other-videos .video-miniature { |
499 | padding-left: 0 !important; | 550 | flex-direction: column; |
500 | |||
501 | /deep/ .video-miniature { | ||
502 | flex-direction: column; | ||
503 | } | ||
504 | } | 551 | } |
505 | 552 | ||
506 | .privacy-concerns { | 553 | .privacy-concerns { |
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 adb728aba..cedbbf985 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts | |||
@@ -50,6 +50,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
50 | 50 | ||
51 | player: any | 51 | player: any |
52 | playerElement: HTMLVideoElement | 52 | playerElement: HTMLVideoElement |
53 | theaterEnabled = false | ||
53 | userRating: UserVideoRateType = null | 54 | userRating: UserVideoRateType = null |
54 | video: VideoDetails = null | 55 | video: VideoDetails = null |
55 | descriptionLoading = false | 56 | descriptionLoading = false |
@@ -572,6 +573,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
572 | 573 | ||
573 | this.zone.runOutsideAngular(async () => { | 574 | this.zone.runOutsideAngular(async () => { |
574 | this.player = await PeertubePlayerManager.initialize(mode, options) | 575 | this.player = await PeertubePlayerManager.initialize(mode, options) |
576 | this.theaterEnabled = this.player.theaterEnabled | ||
577 | |||
575 | this.player.on('customError', ({ err }: { err: any }) => this.handleError(err)) | 578 | this.player.on('customError', ({ err }: { err: any }) => this.handleError(err)) |
576 | 579 | ||
577 | this.player.on('timeupdate', () => { | 580 | this.player.on('timeupdate', () => { |
@@ -589,6 +592,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
589 | this.zone.run(() => this.navigateToNextPlaylistVideo()) | 592 | this.zone.run(() => this.navigateToNextPlaylistVideo()) |
590 | } | 593 | } |
591 | }) | 594 | }) |
595 | |||
596 | this.player.on('theaterChange', (_: any, enabled: boolean) => { | ||
597 | this.zone.run(() => this.theaterEnabled = enabled) | ||
598 | }) | ||
592 | }) | 599 | }) |
593 | 600 | ||
594 | this.setVideoDescriptionHTML() | 601 | this.setVideoDescriptionHTML() |
diff --git a/client/src/assets/player/videojs-components/theater-button.ts b/client/src/assets/player/videojs-components/theater-button.ts index 1e11a9546..bf383cf34 100644 --- a/client/src/assets/player/videojs-components/theater-button.ts +++ b/client/src/assets/player/videojs-components/theater-button.ts | |||
@@ -16,8 +16,11 @@ class TheaterButton extends Button { | |||
16 | const enabled = getStoredTheater() | 16 | const enabled = getStoredTheater() |
17 | if (enabled === true) { | 17 | if (enabled === true) { |
18 | this.player_.addClass(TheaterButton.THEATER_MODE_CLASS) | 18 | this.player_.addClass(TheaterButton.THEATER_MODE_CLASS) |
19 | |||
19 | this.handleTheaterChange() | 20 | this.handleTheaterChange() |
20 | } | 21 | } |
22 | |||
23 | this.player_.theaterEnabled = enabled | ||
21 | } | 24 | } |
22 | 25 | ||
23 | buildCSSClass () { | 26 | buildCSSClass () { |
@@ -25,13 +28,17 @@ class TheaterButton extends Button { | |||
25 | } | 28 | } |
26 | 29 | ||
27 | handleTheaterChange () { | 30 | handleTheaterChange () { |
28 | if (this.isTheaterEnabled()) { | 31 | const theaterEnabled = this.isTheaterEnabled() |
32 | |||
33 | if (theaterEnabled) { | ||
29 | this.controlText('Normal mode') | 34 | this.controlText('Normal mode') |
30 | } else { | 35 | } else { |
31 | this.controlText('Theater mode') | 36 | this.controlText('Theater mode') |
32 | } | 37 | } |
33 | 38 | ||
34 | saveTheaterInStore(this.isTheaterEnabled()) | 39 | saveTheaterInStore(theaterEnabled) |
40 | |||
41 | this.player_.trigger('theaterChange', theaterEnabled) | ||
35 | } | 42 | } |
36 | 43 | ||
37 | handleClick () { | 44 | handleClick () { |
diff --git a/server/tests/api/users/user-notifications.ts b/server/tests/api/users/user-notifications.ts index 6c6d208f5..d573bf024 100644 --- a/server/tests/api/users/user-notifications.ts +++ b/server/tests/api/users/user-notifications.ts | |||
@@ -216,7 +216,7 @@ describe('Test users notifications', function () { | |||
216 | }) | 216 | }) |
217 | 217 | ||
218 | it('Should send a new video notification on a remote scheduled publication', async function () { | 218 | it('Should send a new video notification on a remote scheduled publication', async function () { |
219 | this.timeout(20000) | 219 | this.timeout(50000) |
220 | 220 | ||
221 | // In 2 seconds | 221 | // In 2 seconds |
222 | let updateAt = new Date(new Date().getTime() + 2000) | 222 | let updateAt = new Date(new Date().getTime() + 2000) |
@@ -238,7 +238,7 @@ describe('Test users notifications', function () { | |||
238 | it('Should not send a notification before the video is published', async function () { | 238 | it('Should not send a notification before the video is published', async function () { |
239 | this.timeout(20000) | 239 | this.timeout(20000) |
240 | 240 | ||
241 | let updateAt = new Date(new Date().getTime() + 100000) | 241 | let updateAt = new Date(new Date().getTime() + 1000000) |
242 | 242 | ||
243 | const data = { | 243 | const data = { |
244 | privacy: VideoPrivacy.PRIVATE, | 244 | privacy: VideoPrivacy.PRIVATE, |