I am having a couple issues using the HTML <audio> and <video> tags on iOS devices.
For one, the audio controls only include the play/pause button and volume (and – while playing – the Airplay control). No seek control or duration.
Secondly, the controls occasionally become invisible, though they continue to work. This one also applies to video files.
Below is a screenshot depicting the issue. It may be worth pointing out that both audio and video are presented in a segment, and sometimes switching segments back and forth will fix the issue.
I’m using Capacitor and trying to minimize the use of compatible Cordova plugins, and I’d also like to avoid rolling my own media controls (which I’ve done in previous versions of the app).
UPDATE: This helps reduce the occurrence of disappearing controls, but doesn’t work 100% of the time
For what it’s worth, I was able to prevent the disappearing controls, by adding a a function to the ionChange event…
<ion-segment (ionChange)="segmentChange()">
I wrapped each audio and video element in a container div with a hidden attribute and set controls and preload to false…
<!-- e.g. -->
<div class="audio-container" hidden>
<audio controls="false" preload="none">
<source type="audio/mpeg" [src]="file.url" />
[unable to load audio]
</audio>
</div>
Then I set a short timeout to remove the hidden attribute, change the controls and preload attributes, and reload the inner HTML content, which means the player and controls get rendered on demand each time.
segmentChange() {
if (this.media_segment == "audio" || this.media_segment == "video") {
setTimeout(() => {
let elems = <NodeList>(
document.querySelectorAll("." + this.media_segment + "-container")
);
for (var i = 0; i < elems.length; i++) {
let elem = <HTMLAnchorElement>elems[i];
let inner = elem.innerHTML;
elem.innerHTML = inner;
elem.children[0].setAttribute("controls", "true");
elem.children[0].setAttribute("preload", "metadata");
elem.removeAttribute("hidden");
}
}, 300);
}
}
Still, if anyone can figure out how to force the duration and seek controls to show up, please share.
Below are some details, which may or may not be helpful, given that my data has a specific structure that probably differs from yours. But if you can abstract it a little, it should make sense.
Here’s how I marked up the page template.:
<ion-list *ngIf="media && media.audio.length">
<ion-item *ngFor="let file of media.audio; let i = index">
<ion-label>
<h3
class="ion-text-wrap"
[innerHTML]="file.title"
></h3>
<p
[innerHTML]="file.description"
class="ion-text-wrap"
></p>
<!-- player -->
<ion-grid class="audio-container">
<ion-row class="ion-align-items-center">
<ion-col size="auto">
<ion-button
aria-label="play"
*ngIf="nowPlayingAudioIndex !== i || audioIsPaused"
[color]="nowPlayingAudioIndex == i ? 'primary' : 'medium'"
(click)="playPlauseAudio(i, file.url)"
>
<ion-icon
ios="play-outline"
md="play-sharp"
slot="icon-only"
></ion-icon>
</ion-button>
<ion-button
aria-label="pause"
*ngIf="nowPlayingAudioIndex == i && !audioIsPaused"
[color]="nowPlayingAudioIndex == i ? 'primary' : 'medium'"
(click)="playPlauseAudio(i, file.url)"
>
<ion-icon
ios="pause-outline"
md="pause-sharp"
slot="icon-only"
></ion-icon>
</ion-button>
</ion-col>
<ion-col>
<ion-range
#range
min="0"
[max]="nowPlayingAudioIndex == i ? audioDuration : 100"
[value]="nowPlayingAudioIndex == i ? audioProgress : 0"
[color]="nowPlayingAudioIndex == i ? 'primary' : 'medium'"
pin="false"
mode="md"
debounce="0"
(touchstart)="pauseWhileSeeking(i)"
(mousedown)="pauseWhileSeeking(i)"
(touchend)="seek(i,$event)"
(mouseup)="seek(i, $event)"
></ion-range>
</ion-col>
<ion-col size="auto">
<ion-text
class="timestamp"
[color]="nowPlayingAudioIndex == i ? 'dark' : 'medium'"
>{{nowPlayingAudioIndex == i && audioPlayer?
getAudioProgress(audioPlayer.currentTime) :
'00:00'}}</ion-text
>
</ion-col>
</ion-row>
</ion-grid>
<!-- end player -->
</ion-label>
</ion-item>
</ion-list>