I am making a music player app in Ionic V3 with a player component that slides up from any parent view that includes the component in the template. I am passing the time position/song title from the parent as an @Input and that is working fine.
The problem is that I have a “timeline” which includes a position bar and a pin that represents the current position. I am trying to update the time of the song on the UI when I drag the pin in the player, but the time won’t update on the UI from within a HammerJS function (this is how I am handling the drag event to move the pin).
I added a sample button in the child component to see if updating the UI from outside of the HammerJS function would work, and that updates fine (changes the time string to ‘Hello inside updatePos function`’). I’ve also tried utilizing NgZone in the HammerJS function and that didn’t work.
Parent Page Component Reference
<page-player [activeSongName]="activeSongName" [(trackPlayerPosition)]="trackPosition" (playPause)="togglePlaybackParent($event)"></page-player>
Child Component (Music Player)
<div ion-fixed id="myElement">
<p>{{ activeSongName }}</p>
<!-- Dummy button to test updating position outside of HammerJS -->
<button (click)="updatePos()">Update Position</button>
<div class="musicControls">
<ion-icon size="medium" name="shuffle-outline"></ion-icon>
<ion-icon size="medium" name="skip-backward-outline"></ion-icon>
<ion-icon size="medium" *ngIf="isPlayingTrack" name="play-outline" (click)="togglePlayback(!isPlayingTrack)"></ion-icon>
<ion-icon size="medium" *ngIf="!isPlayingTrack" name="pause-outline" (click)="togglePlayback(!isPlayingTrack)"></ion-icon>
<ion-icon size="medium" name="skip-forward-outline"></ion-icon>
<ion-icon size="medium" name="repeat-outline"></ion-icon>
</div>
<div class="holder">
<div class="audio green-audio-player">
<div class="controls">
<p class="current-time">{{ trackPlayerPosition }}</p>
<div class="slider" id="slider">
<div class="progress">
<div class="pin" id="pin"></div>
</div>
</div>
<span class="total-time">{{ trackLength }}</span>
</div>
</div>
</div>
</div>
Child Component (Music Player) TypeScript
import { Component, EventEmitter, Input, Output, NgZone } from '@angular/core';
import { IonicPage, NavController, NavParams, Platform } from 'ionic-angular';
import Hammer from 'hammerjs';
import * as $ from 'jquery'
/**
* Generated class for the PlayerPage page.
*
* See https://ionicframework.com/docs/components/#navigation for more info on
* Ionic pages and navigation.
*/
@IonicPage()
@Component({
selector: 'page-player',
templateUrl: 'player.html',
})
export class PlayerPage {
_trackPlayerPosition: string
@Input()
set trackPlayerPosition(position: string) {
this._trackPlayerPosition = (position);
}
@Output() postitionEmitter = new EventEmitter<string>();
get trackPlayerPosition(): string { return this._trackPlayerPosition; }
height: any
lastPosX = 0;
isPinDragging: boolean = false
sliderLeftBound: number
sliderRightBound: number
trackPosition: string = "0:00"
trackLength: string = "3:21"
trackLengthNum: number = 3.35
constructor(public zone: NgZone, public platform: Platform) {
this.platform.ready().then(() => {
this.height = this.platform.height()
})
}
ionViewDidLoad() {
var pin = document.getElementById('pin');
var slider = document.getElementById('slider');
this.sliderLeftBound = slider.offsetLeft;
this.sliderRightBound = slider.offsetWidth + slider.offsetLeft;
if (pin != null) {
var hammerPin = new Hammer(pin);
hammerPin.add( new Hammer.Pan({ direction: Hammer.DIRECTION_HORIZONTAL, threshold: 0 }))
hammerPin.on("pan", this.pinDrag.bind(this), this.pinDrag)
} else {
console.log("Error getting pin")
}
}
emitPosition(position: string) {
console.log("Emitting position: " + position)
this.postitionEmitter.emit(position)
}
updatePos() {
// This correctlty updates the time position stirng to "Hello"
this.trackPlayerPosition = "Hello"
}
timeToString(time) {
var minutes = Math.floor(time/1)
var seconds = Math.floor((time%1)*60)
// I have tried both inside and outside the zone
this.zone.run(() => {
console.log("Inside Zone")
this.trackPosition = minutes + ":" + seconds
})
// failed attempt at emitting the position
// this.postitionEmitter.emit(this.newTrackPosition);
// Outputs the correct track position
console.log("New Track Time: " + this.trackPosition)
}
pinDrag(ev) {
var elem = ev.target;
var sliderPosition = ev.center.x
var relativePosition = sliderPosition / this.sliderRightBound
// Outputs the correct position in the logs
console.log("relativePosition: " + relativePosition * this.trackLengthNum)
if (ev.center.x >= this.sliderLeftBound && ev.center.x <= this.sliderRightBound) {
if ( ! this.isPinDragging ) {
this.isPinDragging = true;
this.lastPosX = elem.offsetLeft;
}
var posX = ev.deltaX + this.lastPosX;
elem.style.left = posX + "px";
if (ev.isFinal) {
this.isPinDragging = false
this.timeToString(relativePosition * this.trackLengthNum)
// Outputs the correct position in the logs
console.log("End drag pin: " + this.trackPosition)
// Does NOT update the trackPosition on the UI
this.trackPlayerPosition = this.trackPosition
}
}
}
}