Create your own Mini Player

In this tutorial, we will explain how to create your own mini player on your iOS project.

Goal

Our goal here is to create a Mini Player like below by yourself, it will be a code base to allow you to set create a custom Mini Player.

We can describe the player as follows :

  • On the top, a progress bar to display the current STTrack progression

  • On the left, the image of the current STTrack

  • The first line is the title of the current STTrack

  • The second line is the title of the current STContent

  • On the right, the main button to control the STPlayer

Set up the UI

To redo the design, we have to first create our custom UIView by adding a XIB file with his implementation:

  • MiniPlayer.xib

  • MiniPlayer.swift

Within the XIB, we have to add those elements:

We should map the IBOutlets with our MiniPlayer.swift file:

MiniPlayer.swift
import UIKit
final class MiniPlayer: UIView {
@IBOutlet private weak var imageView: UIImageView!
@IBOutlet private weak var progressView: UIProgressView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var subtitleLabel: UILabel!
@IBOutlet private weak var playButton: UIButton!
@IBOutlet private weak var pauseButton: UIButton!
@IBOutlet private weak var openExpandButton: UIButton!
}

Now we have to map the user interactions with our implementation:

MiniPlayer.swift
@IBAction private func didTapPlay(_ sender: Any) {
}
@IBAction private func didTapPause(_ sender: Any) {
}
@IBAction private func didTapOpenMainPage(_ sender: Any) {
}

Connect to the SDK

Now that we have a complete UIView, we need to connect the elements with the SDK values.

Implement STPlayer feature

Let's start by adding the STPlayer feature within our view by folloing those points:

  • Import the SDK

  • Observing the STPlayer changes

MiniPlayer.swift
import UIKit
import StayTunedSDK
final class MiniPlayer: UIView {
private var player: STPlayer? = STPlayer.shared
@IBOutlet private weak var imageView: UIImageView!
@IBOutlet private weak var progressView: UIProgressView!
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var subtitleLabel: UILabel!
@IBOutlet private weak var playButton: UIButton!
@IBOutlet private weak var pauseButton: UIButton!
@IBOutlet private weak var openExpandButton: UIButton!
override func awakeFromNib() {
self.player?.add(observer: self)
}
}
extension MiniPlayer: STPlayerObserver {
func playerCurrentTrackDidChange(to track: STTrack) {
}
func playerCurrentContentDidChange(to content: STContent) {
}
func playerCurrentTimeDidChange(to value: Double) {
}
func playerStateDidChange(to state: STPlayerState) {
}
}

Retrieve all information needed

We are going to set the image of the track, the title and subtitle of the view by observing the changes of a track or a content:

MiniPlayer.swift
extension MiniPlayer: STPlayerObserver {
private func setCurrentMedia() {
guard let track = self.player.currentTrack,
let content = self.player.currentContent else {
return
}
guard let imageUrl = URL(string: track.imgSrc ?? ""),
let imageData = try? Data(contentsOf: imageUrl) else {
return
}
self.imageView.image = UIImage(data: imageData)
self.titleLabel.text = track.title
self.subtitleLabel.text = content.title
}
// The track of the player did change
func playerCurrentTrackDidChange(to track: STTrack) {
self.setCurrentMedia()
}
// The content of the player did change
func playerCurrentContentDidChange(to content: STContent) {
self.setCurrentMedia()
}
}

We also need to listen to the playhead of the listening track to populate our progress view:

MiniPlayer.swift
extension MiniPlayer: STPlayerObserver {
func playerCurrentTimeDidChange(to value: Double) {
guard let time = self.player.currentTime,
let duration = self.player.getAudioDuration() else {
return
}
let progression = Float(time) / Float(duration)
self.progressView.setProgress(progression, animated: false)
}
}

Now we have to manage our Play and Pause button by observing the state of the player:

MiniPlayer.swift
extension MiniPlayer: STPlayerObserver {
private func setPlayingButton(hidden: Bool) {
self.playButton.isHidden = hidden
self.pauseButton.isHidden = !hidden
}
func playerStateDidChange(to state: STPlayerState) {
switch state {
case .playing, loading:
self.setPlayingButton(hidden: false)
case .paused:
self.setPlayingButton(hidden: true)
@unknown default:
fatalError()
}
}
}

Send user interactions to the STPlayer

We want to allow our user to press Play or Pause a track or to open the expand if he taps on the Mini Player. To send those interactions we have to implement our IBAction like this:

MiniPlayer.swift
@IBAction private func didTapPlay(_ sender: Any) {
self.player.resume()
}
@IBAction private func didTapPause(_ sender: Any) {
self.player.stop()
}
@IBAction private func didTapOpenMainPage(_ sender: Any) {
if let parentController = next as? UIViewController {
STExpand.shared?.present(from: parentController)
}
}

Resources

You can retrieve all the codes from our demo app: https://github.com/StayTunedAds/staytuned-ios-sdk

Mini Player implementation: https://github.com/StayTunedAds/staytuned-ios-sdk/tree/master/SDKDemoApp/SDKDemoApp/Views/MiniPlayer

The Mini Player has been implemented by using the Clean Swift (VIP) architecture in the demo app. More information are available here: https://clean-swift.com