Implementing an Audio player in Flutter with Subtitles in under 5 min

Implementing an Audio player in Flutter with Subtitles in under 5 minutes.

Prerequisites:

  • Basic understanding of Flutter development
  • Familiarity with HTTP requests and networking concepts

Packages:

  • audioplayers: For audio playback functionality
  • srt_parser_2: For subtitle parsing and timing
  • http: For fetching audio and subtitle files from the network

Steps:

  1. Set up your Flutter project:
flutter pub add audioplayers srt_parser_2 http

2. Create the UI:

  • Design a layout that accommodates the audio player, subtitle display, and scrolling functionality. Consider using a Column or Stack widget to arrange the elements.
  • Create a Text widget or a custom subtitle widget to display the current subtitle.
  • Use a ListView.builder or a similar scrollable widget to display the subtitles, ensuring smooth scrolling as the audio progresses.

3. Fetch audio and subtitle files:

  • Implement functions to fetch the audio and subtitle files from their network locations using the http package. Handle potential errors and provide feedback to the user.

4. Parse subtitles:

  • Use the srt_parser_2 package to parse the downloaded subtitle file into a list of SrtSubtitle objects, which contain start and end times for each subtitle text.

5. Create an AudioPlayer instance:

  • Instantiate an AudioPlayer object:
AudioPlayer audioPlayer = AudioPlayer();

Use a Stream or Timer to periodically check the audio player’s current position:

Stream<Duration> positionStream = audioPlayer.onAudioPositionChanged;
positionStream.listen((position) {
  // Find the current subtitle based on the audio position
  SrtSubtitle? currentSubtitle = findCurrentSubtitle(position, subtitles);

  // If a subtitle is found, update the display
  if (currentSubtitle != null) {
    setState(() {
      currentSubtitleText = currentSubtitle.text;
    });
    // Scroll the subtitle list to the current item if necessary
    // (implementation depends on your chosen scrollable widget)
  }
});

Dispose of resources:

  • When the widget is no longer needed, dispose of the AudioPlayer object and any other resources to avoid memory leaks:
@override
void dispose() {
  audioPlayer.dispose();
  super.dispose();
}

Complete code example:

import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:srt_parser_2/srt_parser_2.dart';
import 'package:http/http.dart' as http;

class AudioPlayerWithSubtitles extends StatefulWidget {
  final String audioUrl;
  final String subtitleUrl;

  const AudioPlayerWithSubtitles({Key? key, required this.audioUrl, required this.subtitleUrl}) : super(key: key);

  @override
  _AudioPlayerWithSubtitlesState createState() => _AudioPlayerWithSubtitlesState();
}

class _AudioPlayerWithSubtitlesState extends State<AudioPlayerWithSubtitles> {
  String currentSubtitleText = '';
  List<SrtSubtitle> subtitles = [];
  AudioPlayer audioPlayer = AudioPlayer();

  @override
  void initState() {
    super.initState();
    _fetchSubtitles();
    _playAudio();
  }

  void _fetchSubtitles() async {
    try {
      final response = await http.get(Uri.parse(widget.subtitleUrl));
      if (response.statusCode == 200) {
        final srtContent = response.body;
        subtitles = parseSrt(srtContent);
      } else {
        // Handle error
      }
    } catch (error) {
      // Handle error
    }
  }

  void _playAudio() async {
    try {
      await audioPlayer.play(widget.audioUrl);
      final positionStream = audioPlayer.onAudioPositionChanged;
      positionStream.listen((position) {

Here’s the findCurrentSubtitle method you can use to identify the current subtitle based on the audio position:

SrtSubtitle? findCurrentSubtitle(Duration position, List<SrtSubtitle> subtitles) {
  for (final subtitle in subtitles) {
    if (position >= subtitle.startTime && position <= subtitle.endTime) {
      return subtitle;
    }
  }
  return null; // No matching subtitle found
}

This method iterates through the list of SrtSubtitle objects and compares the audio position (position) with the startTime and endTime of each subtitle. If the position falls within the time range of a subtitle, that subtitle is returned. Otherwise, null is returned, indicating that no matching subtitle was found.

Explanation:

  • position: The current position of the audio playback.
  • subtitles: The list of parsed subtitle objects.
  • for loop: Iterates through each subtitle in the list.
  • if condition: Checks if the audio position is within the start and end time range of the current subtitle.
  • return subtitle: If the condition is met, the current subtitle is returned.
  • return null: If no matching subtitle is found, null is returned to indicate that there’s no active subtitle at the current position.

Remember to integrate this method within the positionStream listener to update the displayed subtitle as the audio progresses.

Scrolling the list view to the current subtitle

Scrolling the list view to the current subtitle can be achieved by adding some logic to your findCurrentSubtitle method and your scrollable widget. Here’s how you can do it:

1. Modifying findCurrentSubtitle:

Instead of just returning the SrtSubtitle object, we can enhance it to include the scroll index of the corresponding subtitle in the list view:

SrtSubtitleWithIndex? findCurrentSubtitle(Duration position, List<SrtSubtitle> subtitles) {
  for (int i = 0; i < subtitles.length; i++) {
    final subtitle = subtitles[i];
    if (position >= subtitle.startTime && position <= subtitle.endTime) {
      return SrtSubtitleWithIndex(subtitle: subtitle, index: i);
    }
  }
  return null; // No matching subtitle found
}

his modified method now creates a SrtSubtitleWithIndex object that holds both the SrtSubtitle and its corresponding index in the list view.

2. Integrating with Scrollable Widget:

Next, update your positionStream listener to utilize the scroll index information:

positionStream.listen((position) {
  final currentSubtitleWithIndex = findCurrentSubtitle(position, subtitles);

  if (currentSubtitleWithIndex != null) {
    setState(() {
      currentSubtitleText = currentSubtitleWithIndex.subtitle.text;
    });

    // Scroll the list view to the appropriate item
    _scrollToItem(currentSubtitleWithIndex.index);
  }
});

You’ll need to implement the _scrollToItem method based on your chosen scrollable widget:

For ListView.builder:

void _scrollToItem(int index) {
  // Use a ScrollController to control the list view
  final controller = ScrollController();

  // Calculate the item's offset based on its index and estimated item height
  final offset = index * estimatedItemHeight;

  // Animate the scroll to the desired offset
  controller.animateTo(offset,
      duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
}

Replace estimatedItemHeight with the actual or estimated height of your subtitle items.

For other scrollable widgets, use their respective scrolling APIs.

Remember to dispose of the ScrollController when the widget is no longer needed.

Additional considerations:

  • Adjust the scrolling animation duration and behavior as needed.
  • Optimize item height estimation for smooth scrolling.
  • Handle potential edge cases (e.g., first or last item).

By following these steps, you can effectively scroll your list view to the current subtitle as the audio plays, enhancing the user experience of your audio player with subtitles.

We recommend AutoSizeText to display subtitle. Because if set estimatedItemHeight=100 then AutoSizeText will try its best to fit the text into that height this is what we want.

Row(
                        children: <Widget>[
                          Expanded(
                              // Constrains AutoSizeText to the width of the Row
                              child: AutoSizeText(
                            subtitle.rawLines.join(),
                            minFontSize: 16,
                            maxFontSize: 20,
                            maxLines: 10,
                            overflow: TextOverflow.ellipsis,
                            style: TextStyle(
                              fontSize: subtitle == _currentSubtitle ? 20 : 16,
                              fontWeight: subtitle == _currentSubtitle
                                  ? FontWeight.w900
                                  : FontWeight.w500,
                              color: subtitle == _currentSubtitle
                                  ? Colors.black
                                  : Colors.grey,
                              shadows: StringUtils.getGoldenShadow(),
                            ),
                          ))
                        ],
                      )

Thanks for reading the post.Hope it has help you to get the idea to implement the Audio player in Flutter with Subtitles

Leave a Reply