/// /// Copyright 2015-2026 Micro Focus or one of its affiliates. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// import { AsyncPipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, inject, OnDestroy, OnInit } from '@angular/core'; import { Subject, timer } from 'rxjs'; import { switchMap, takeUntil } from 'rxjs/operators'; import { FocusIndicatorDirective } from '../../../../directives/accessibility/focus-indicator/focus-indicator.directive'; import { FocusWithinDirective } from '../../../../directives/accessibility/focus-within/focus-within.directive'; import { TabbableListItemDirective } from '../../../../directives/accessibility/tabbable-list/tabbable-list-item.directive'; import { TabbableListDirective } from '../../../../directives/accessibility/tabbable-list/tabbable-list.directive'; import { ClickOutsideDirective } from '../../../../directives/click-outside/click-outside.directive'; import { IconComponent } from '../../../icon/icon.component'; import { SliderOptions, SliderSize } from '../../../slider/index'; import { SliderComponent } from '../../../slider/slider.component'; import { TooltipDirective } from '../../../tooltip/tooltip.directive'; import { MediaPlayerService } from '../../media-player.service'; let uniqueId: number = 1; @Component({ selector: 'ux-media-player-controls', templateUrl: './controls.component.html', changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.quiet]': 'mediaPlayerService.quietMode || mediaPlayerService.fullscreen', }, imports: [ FocusWithinDirective, FocusIndicatorDirective, TooltipDirective, IconComponent, SliderComponent, ClickOutsideDirective, TabbableListDirective, TabbableListItemDirective, AsyncPipe, ], }) export class MediaPlayerControlsExtensionComponent implements OnInit, OnDestroy { readonly mediaPlayerService = inject(MediaPlayerService); volumeActive: boolean = false; volumeFocus: boolean = false; returnFocus: boolean = true; subtitlesId: string = `ux-media-player-subtitle-popover-${uniqueId++}`; subtitlesOpen: boolean = false; mouseEnterVolume = new Subject(); mouseLeaveVolume = new Subject(); options: SliderOptions = { handles: { aria: { thumb: 'Volume', }, }, track: { colors: { lower: '#666', }, height: SliderSize.Narrow, ticks: { major: { show: false, }, minor: { show: false, }, }, }, }; private _volume: number = 100; private _previousVolume = 100; private readonly _onDestroy = new Subject(); get volume(): number { return this._volume; } set volume(value: number) { if (value === 0 && this._volume !== 0) { this._previousVolume = this._volume; } this._volume = Math.min(Math.max(value, 0), 100); this.mediaPlayerService.volume = this._volume / 100; } ngOnInit(): void { this.mediaPlayerService.volumeChangeEvent .pipe(takeUntil(this._onDestroy)) .subscribe(volume => (this.volume = volume * 100)); this.mediaPlayerService.initEvent .pipe(takeUntil(this._onDestroy)) .subscribe(() => (this.volume = this.mediaPlayerService.volume * 100)); this.mouseEnterVolume .pipe(takeUntil(this._onDestroy)) .subscribe(() => (this.volumeActive = true)); this.mouseLeaveVolume .pipe( switchMap(() => timer(1500).pipe(takeUntil(this.mouseEnterVolume))), takeUntil(this._onDestroy) ) .subscribe(() => (this.volumeActive = false)); } ngOnDestroy(): void { this._onDestroy.next(); this._onDestroy.complete(); } toggleMute(): void { this.volume = this.volume === 0 ? this._previousVolume : 0; } goToStart(): void { this.mediaPlayerService.currentTime = 0; } goToEnd(): void { this.mediaPlayerService.currentTime = this.mediaPlayerService.duration; } isSubtitleActive(): boolean { for (let idx = 0; idx < this.mediaPlayerService.textTracks.length; idx++) { if (this.mediaPlayerService.textTracks[idx].mode === 'showing') { return true; } } return false; } setSubtitleTrack(track: TextTrack): void { // hide all tracks this.mediaPlayerService.hideSubtitleTracks(); // set the position of the subtitle track for (let idx = 0; idx < track.cues.length; idx++) { const cue: any = track.cues[idx]; cue.line = -3; } // activate the selected one track.mode = 'showing'; } getSubtitleTrack(): string { for (let idx = 0; idx < this.mediaPlayerService.textTracks.length; idx++) { if (this.mediaPlayerService.textTracks[idx].mode === 'showing') { return this.mediaPlayerService.textTracks[idx].label; } } return this.mediaPlayerService.noSubtitlesAriaLabel; } }