Developing your own video player is a classic task that almost every Android developer faces who wants to delve deeper into working with multimedia. At first glance, it may seem that it is enough to simply use a standard component VideoView, but this is not enough to create a high-quality product. Modern users expect smooth playback, support for complex codecs and intuitive gesture controls, which standard solutions often do not provide.
The process of creating an application begins with choosing the right architecture. There are many libraries in the Android ecosystem, but the de facto industry standard is ExoPlayer (now Jetpack Media3). This library, developed by Google, provides flexibility not available with built-in solutions and makes it easy to customize the interface to suit any need. If you plan to write a player from scratch based on MediaPlayer API, be prepared for difficulties with buffering and format support on different devices.
In this article we will analyze the key stages of implementation: from setting up dependencies to processing swipes to adjust brightness and sound. We will touch on battery optimization and hardware acceleration, which is critical for stable operation on budget smartphones. Understanding these principles will allow you to create an application that not only plays video, but does it efficiently and reliably.
Selecting an architecture and connecting libraries
The first step in development is determining the technology stack. The use of native MediaPlayer is justified only in the simplest cases, when you need to play one file without complex controls. For a full-fledged player that supports adaptive streaming (DASH, HLS) and complex caching scenarios, it is necessary to implement more powerful solutions. The architecture of your application should separate the logic of playback and display of the interface.
The most reasonable choice today is the integration of the library AndroidX Media3, which replaced the classic ExoPlayer. It provides a modular structure, allowing you to connect only the necessary components. This reduces the size of the resulting APK file and makes the code easier to maintain. The connection is made through the Gradle build system, where you specify dependencies for the core, ui and common modules.
When setting up a project, it is important to immediately determine the minimum version of the SDK. To work with modern codecs, such as H.265 (HEVC) or AV1, specific API versions may be required. It is also worth considering that some functions, such as Picture-in-Picture (picture in picture), require a certain version of Android to work correctly without additional crutches.
โ ๏ธ Attention: Versions of Media3 libraries are updated regularly, and initialization methods may change. Always check Google's official documentation for the latest syntax before starting a new development sprint.
After adding dependencies to the file build.gradle, you need to synchronize the project. Make sure you are using compatible versions of all modules to avoid class conflicts during compilation. Incorrect version configuration often leads to errors ClassNotFoundException or NoSuchMethodError during application execution.
Use the version of the library with the suffix "-ktx" to get convenient Kotlin extensions that will significantly reduce the amount of boilerplate code when initializing the player.
Initializing PlayerView and setting up tracks
The main element of the interface with which the user interacts is PlayerView. This component is responsible for displaying the video surface, imposing controls and processing touches. In contrast to the simple one, SurfaceView, PlayerView takes on most of the routine work of managing the display life cycle.
For correct operation, you need to associate the instance ExoPlayer with your PlayerView. This is done in the onCreate or onViewCreated activity/fragment method. It is important to remember to release player resources in the onDestroy or onStopmethod to prevent memory leaks and battery drain in the background.
Setting up tracks requires attention to detail. You need to implement logic for switching between audio tracks and subtitles. For this purpose, TrackSelectoris used, which allows you to filter the available tracks by language, type or bitrate. The user should be able to select the desired audio track through a clear menu.
- ๐ฌ Implement automatic selection of the default track based on the system language settings of the device.
- ๐ Add a visual indicator of the current volume level when changing the sound with gestures.
- ๐ Provide support for external subtitle file formats .srt and .ass.
- โก Use data caching to speed up video restart.
Particular attention should be paid to handling track loading errors. The network may be unstable or the file may be corrupted. Your application should gracefully handle such situations by showing the user a clear error message, rather than simply crashing. Logging these events will help you in further analysis of problems.
Implementation of gesture and interface control
A modern video player cannot be imagined without gesture control. Users are accustomed to adjusting the brightness by swiping on the left side of the screen, and the volume by swiping on the right. Implementing this functionality requires intercepting touch events (onTouchEvent) and calculating the finger movement delta.
Gesture processing logic must be responsive, but not conflict with other interface elements. For example, double tapping is often used to fast forward or rewind 10 seconds. Implementing this behavior requires precise timing and distinguishing between single and double clicks, which can be a non-trivial task.
A component is used to display playback progress. DefaultTimeBar. It must be synchronized with the current position of the player. When dragging the slider (seek), it is important to pause the interface update so as not to create visual noise, and send the rewind command only after the user releases the finger.
player.addListener(object : Player.Listener {override fun onPlaybackStateChanged(state: Int) {
// Update the UI depending on the state: buffering, ready, finished
}
})
Customizing the interface allows you to distinguish your application from competitors. You can change the progress bar colors, timer fonts and button icons. However, you should not overload the screen with unnecessary elements. Minimalism and ergonomics are the key to ease of use in the dark or on the go.
โ ๏ธ Warning: Gesture processing may conflict with standard Android navigation gestures (for example, swipe back). Be sure to test the application on devices with different types of navigation (buttons and gestures).
Working with codecs and hardware acceleration
Effective video playback directly depends on how your player interacts with the hardware capabilities of the device. Software decoding is universal, but it heavily loads the processor and drains the battery quickly. Hardware acceleration (Hardware Acceleration) uses dedicated GPU or DSP units, which is the preferred option.
Media3 library automatically tries to select the best available decoder. However, in some cases, especially on older or specific devices from Chinese manufacturers, the automatic selection may fall on the software decoder. The developer must be able to force the use of hardware codecs through the settings. DefaultTrackSelector.
Support for modern compression standards, such as AV1is becoming increasingly relevant. This codec provides better quality at a lower bitrate, but requires an appropriate chipset in the device. If there is no hardware support, the player must be able to correctly fall back to software decoding or warn the user that playback is impossible.
| Codec | Compression type | CPU load | Android support |
|---|---|---|---|
| H.264 (AVC) | Standard | Low | Full (API 10+) |
| H.265 (HEVC) | High efficiency | Medium/Low | API 21+ (often requires license) |
| VP9 | Open standard | Medium | API 21+ |
| AV1 | Latest standard | High (without HW) | API 29+ (common with API 30) |
When working with 4K content, the load on the system increases many times over. In such scenarios, it is critical to limit the frame rate or resolution if the device cannot cope to avoid frame drops. Real-time performance monitoring will help adapt the quality of the stream.
What to do if the video is slow?
Try disabling hardware acceleration in the player settings. Sometimes drivers for a specific GPU do not work correctly with certain codec profiles, and software decoding turns out to be more stable, although hotter.
Background playback and Picture-in-Picture
Users often want to listen to audio from video lectures or podcasts by turning off the screen or minimizing the application. Implementing background playback requires the use of Foreground Service. This tells the Android system that your app is performing an important task for the user and prevents the garbage collector from killing it.
Picture in Picture (PiP) mode allows the user to continue watching videos while in other apps or on the home screen. Starting with Android 8.0, this feature is available natively, but requires proper configuration in the manifest and handling of the activity lifecycle. When switching to PiP, the interface of your application should be simplified, leaving only video and basic control buttons.
Integration with the system media player (notifications in the curtain, control from a headset or smart watch) is carried out through MediaSession. This is a must-have component for any serious player. Without it, the system will not know that music or video is currently playing, and will not be able to offer the user standard controls.
- ๐ Configure the correct display of the cover and track name in notifications.
- ๐ง React to pressing the Play/Pause button on Bluetooth headsets.
- ๐ฑ Ensure a smooth transition to PiP mode without restarting the stream.
- ๐ Stop the service gracefully when playback ends.
Implementing these features significantly improves usability (UX). The user should not keep the screen on if they only need sound. Ignoring these standards may result in negative reviews in the app store.
Using MediaSession and Foreground Service is a mandatory requirement for publishing a quality media application on Google Play.
Optimizing performance and power consumption
Video players are among the most resource-intensive applications. Improper optimization can cause the device to overheat and drain the battery quickly. The main source of problems is the constant updating of the UI and inefficient work with memory buffers. It is necessary to minimize the number of operations in the main thread (Main Thread).
Use profilers built into Android Studio, such as Profiler and Energy Estimator. They will help you identify areas of code that consume too much energy or cause frame drops (junk). Particular attention should be paid to the refresh rate of the interface: there is no point in redrawing the timer every 16 ms if accuracy to the second is sufficient.
Data caching is another important aspect. If a user is watching a video from the Internet, re-buffering the same sections while rewinding wastes bandwidth and time. Local caching of video fragments to the device's disk allows you to make rewinding instantaneous and saves mobile data.
โ ๏ธ Attention: When caching large amounts of video, make sure that you check the availability of free disk space. Trying to write gigabytes of data to a full drive will cause the application to crash.
It is also worth implementing adaptive change in stream quality (ABR) when playing online content. If the connection speed drops, the player should automatically switch to a lower bitrate to avoid stopping playback for buffering. This requires real-time network bandwidth analysis logic.
โ๏ธ Optimization checklist
Frequently asked questions (FAQ)
What is the minimum Android API that should be specified in project?
It is recommended to install minSdkVersion not lower than 21 (Android 5.0). This covers the vast majority of active devices and allows you to use modern APIs without a lot of compatibility checks. Supporting older versions (16-19) will require significant effort in testing and using legacy libraries.
How to add support for IPTV playlists (m3u8)?
The Media3 library natively supports the HLS format (.m3u8). You just need to send the link to the playlist to MediaItem. To parse complex channel lists from an .m3u file, you can use third-party parser libraries or write a simple regular expression to extract links and titles.
Why does the video play without sound?
Most often the problem lies in the audio stream settings. Make sure you don't set the stream to AUDIO_FOCUS_GAIN_TRANSIENT_MAY_DUCK unnecessarily, and check whether the audio is muted at the system level or the file itself. Also check the permissions if the audio is taken from the microphone (for screen recording).
Can this code be used for iOS?
No, the Kotlin/Java code and AndroidX libraries are specific to the Android platform. For iOS, you'll need to use AVFoundation or third-party players like VLCKit written in Swift or Objective-C. The logic of business rules may be common, but the implementation of the interface and work with media is completely different.
How to protect video from piracy (DRM)?
Widevine DRM technology is used to protect content. ExoPlayer supports working with licensed servers. You'll need to integrate the license request, pass the resulting token to the player, and set up secure key storage. This is a complex task that requires interaction with the content provider.