Advertisement
Guest User

Audio Service Code

a guest
Feb 20th, 2021
201
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 30.33 KB | None | 0 0
  1. import 'dart:async';
  2. import 'package:BlackHole/playlist.dart' as playlist;
  3. import 'package:http/http.dart';
  4. import 'dart:math';
  5. import 'dart:convert';
  6. import 'package:des_plugin/des_plugin.dart';
  7. import 'package:audio_service/audio_service.dart';
  8. import 'package:audio_session/audio_session.dart';
  9. import 'package:flutter/foundation.dart';
  10. import 'package:flutter/material.dart';
  11. import 'package:just_audio/just_audio.dart';
  12. import 'package:rxdart/rxdart.dart';
  13.  
  14. List response = [];
  15. List<MediaItem> queue = [];
  16.  
  17. class TempScreen extends StatefulWidget {
  18. @override
  19. _TempScreenState createState() => _TempScreenState();
  20. }
  21.  
  22. class _TempScreenState extends State<TempScreen> {
  23. @override
  24. Widget build(BuildContext context) {
  25. response = ModalRoute.of(context).settings.arguments;
  26. print('response at initial is $response');
  27. setValues(response) {
  28. // List<MediaItem> tempList = [];
  29. for (int i = 0; i < response.length; i++) {
  30. var tempDict = MediaItem(
  31. id: response[i]['id'],
  32. album: response[i]['more_info']['album'],
  33. duration: Duration(
  34. seconds: int.parse(response[i]['more_info']['duration'])),
  35. title: response[i]['title']
  36. .toString()
  37. .replaceAll("&amp;", "&")
  38. .replaceAll("&#039;", "'")
  39. .replaceAll("&quot;", "\""),
  40. // artist: response[i]["more_info"]["artistMap"]["primary_artists"][0]
  41. // ["name"]
  42. // .toString()
  43. // .replaceAll("&amp;", "&")
  44. // .replaceAll("&#039;", "'")
  45. // .replaceAll("&quot;", "\""),
  46. artUri: response[i]['image']
  47. .toString()
  48. .replaceAll("150x150", "500x500"));
  49. print('tempdict is $tempDict');
  50. queue.add(tempDict);
  51. }
  52. print('queue has been setted');
  53. }
  54.  
  55. print('calling setVlaues');
  56. setValues(response);
  57. print('value of queue after setValues is $queue');
  58. return Container(
  59. decoration: BoxDecoration(
  60. gradient: LinearGradient(
  61. begin: Alignment.topLeft,
  62. end: Alignment.bottomRight,
  63. // stops: [0, 0.2, 0.8, 1],
  64. colors: Theme.of(context).brightness == Brightness.dark
  65. ? [
  66. Colors.grey[850],
  67. Colors.grey[900],
  68. Colors.black,
  69. ]
  70. : [
  71. Colors.white,
  72. Theme.of(context).canvasColor,
  73. ],
  74. ),
  75. ),
  76. child: Scaffold(
  77. backgroundColor: Colors.transparent,
  78. // appBar: AppBar(
  79. // title: Text('Now Playing'),
  80. // centerTitle: true,
  81. // ),
  82. body: SafeArea(
  83. child: SingleChildScrollView(
  84. child: StreamBuilder<bool>(
  85. stream: AudioService.runningStream,
  86. builder: (context, snapshot) {
  87. if (snapshot.connectionState != ConnectionState.active) {
  88. return SizedBox();
  89. }
  90. final running = snapshot.data ?? false;
  91. return Column(
  92. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  93. children: [
  94. if (!running) ...[
  95. audioPlayerButton(),
  96. ] else ...[
  97. Container(
  98. height: MediaQuery.of(context).size.height * 0.0725,
  99. child: Row(
  100. children: [
  101. IconButton(
  102. icon: Icon(Icons.keyboard_arrow_down_rounded),
  103. onPressed: () {
  104. Navigator.pop(context);
  105. }),
  106. ],
  107. ),
  108. ),
  109. Container(
  110. height: MediaQuery.of(context).size.width * 0.925,
  111. child: Hero(
  112. tag: 'image',
  113. child: Card(
  114. elevation: 10,
  115. shape: RoundedRectangleBorder(
  116. borderRadius: BorderRadius.circular(15)),
  117. clipBehavior: Clip.antiAlias,
  118. child: Stack(
  119. children: [
  120. Image(
  121. image: AssetImage('assets/cover.jpg'),
  122. height:
  123. MediaQuery.of(context).size.width * 0.925,
  124. ),
  125. StreamBuilder<QueueState>(
  126. stream: _queueStateStream,
  127. builder: (context, snapshot) {
  128. final queueState = snapshot.data;
  129. // final queue = queueState?.queue ?? [];
  130. final mediaItem = queueState?.mediaItem;
  131. return mediaItem == null
  132. ? Image(
  133. image: AssetImage(
  134. 'assets/cover.jpg'),
  135. height: MediaQuery.of(context)
  136. .size
  137. .width *
  138. 0.925,
  139. )
  140. : Image(
  141. image: NetworkImage(
  142. mediaItem.artUri),
  143. height: MediaQuery.of(context)
  144. .size
  145. .width *
  146. 0.925,
  147. );
  148. }),
  149. ],
  150. ),
  151. ),
  152. ),
  153. ),
  154.  
  155. //Title and subtitle
  156.  
  157. Container(
  158. height: (MediaQuery.of(context).size.height * 0.9 -
  159. MediaQuery.of(context).size.width * 0.925) /
  160. 3,
  161. child: Padding(
  162. padding: const EdgeInsets.fromLTRB(15, 25, 15, 0),
  163. child: StreamBuilder<QueueState>(
  164. stream: _queueStateStream,
  165. builder: (context, snapshot) {
  166. final queueState = snapshot.data;
  167. final mediaItem = queueState?.mediaItem;
  168. return Column(
  169. mainAxisAlignment: MainAxisAlignment.end,
  170. children: [
  171. // Title container
  172. Container(
  173. height:
  174. (MediaQuery.of(context).size.height *
  175. 0.9 -
  176. MediaQuery.of(context)
  177. .size
  178. .width *
  179. 0.925) *
  180. 2 /
  181. 14.0,
  182. child: FittedBox(
  183. child: Text(
  184. (mediaItem?.title != null)
  185. ? (mediaItem.title)
  186. : 'Unknown',
  187. textAlign: TextAlign.center,
  188. style: TextStyle(
  189. fontSize: 50,
  190. fontWeight: FontWeight.bold,
  191. color:
  192. Theme.of(context).accentColor),
  193. )),
  194. ),
  195.  
  196. //Subtitle container
  197. Container(
  198. height:
  199. (MediaQuery.of(context).size.height *
  200. 0.9 -
  201. MediaQuery.of(context)
  202. .size
  203. .width *
  204. 0.925) *
  205. 1 /
  206. 16.0,
  207. child: Text(
  208. (mediaItem?.artist != null)
  209. ? (mediaItem.artist)
  210. : 'Unknown',
  211. textAlign: TextAlign.center,
  212. style: TextStyle(
  213. fontWeight: FontWeight.w500),
  214. overflow: TextOverflow.ellipsis,
  215. ),
  216. ),
  217. ],
  218. );
  219. }),
  220. ),
  221. ),
  222. //Seekbar starts from here
  223. Container(
  224. height: (MediaQuery.of(context).size.height * 0.9 -
  225. MediaQuery.of(context).size.width * 0.925) /
  226. 3.5,
  227. child: Column(
  228. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  229. children: [
  230. StreamBuilder<MediaState>(
  231. stream: _mediaStateStream,
  232. builder: (context, snapshot) {
  233. final mediaState = snapshot.data;
  234.  
  235. return SeekBar(
  236. duration: mediaState?.mediaItem?.duration ??
  237. Duration.zero,
  238. position:
  239. mediaState?.position ?? Duration.zero,
  240. onChangeEnd: (newPosition) {
  241. AudioService.seekTo(newPosition);
  242. },
  243. );
  244. },
  245. ),
  246. ],
  247. ),
  248. ),
  249.  
  250. Container(
  251. height: (MediaQuery.of(context).size.height * 0.9 -
  252. MediaQuery.of(context).size.width * 0.925) /
  253. 3,
  254. child: Row(
  255. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  256. crossAxisAlignment: CrossAxisAlignment.center,
  257. children: [
  258. StreamBuilder<QueueState>(
  259. stream: _queueStateStream,
  260. builder: (context, snapshot) {
  261. final queueState = snapshot.data;
  262. final queue = queueState?.queue ?? [];
  263. final mediaItem = queueState?.mediaItem;
  264. return (queue != null && queue.isNotEmpty)
  265. ? IconButton(
  266. icon: Icon(Icons.skip_previous_rounded),
  267. iconSize: 45.0,
  268. onPressed: mediaItem == queue.first
  269. ? null
  270. : AudioService.skipToPrevious,
  271. )
  272. : IconButton(
  273. icon: Icon(Icons.skip_previous_rounded),
  274. iconSize: 45.0,
  275. onPressed: null);
  276. },
  277. ),
  278. Stack(
  279. children: [
  280. Center(
  281. child: StreamBuilder<AudioProcessingState>(
  282. stream: AudioService.playbackStateStream
  283. .map((state) => state.processingState)
  284. .distinct(),
  285. builder: (context, snapshot) {
  286. final processingState = snapshot.data ??
  287. AudioProcessingState.none;
  288. return describeEnum(processingState) !=
  289. 'ready'
  290. ? SizedBox(
  291. height: 65,
  292. width: 65,
  293. child:
  294. CircularProgressIndicator(),
  295. )
  296. : SizedBox();
  297. },
  298. ),
  299. ),
  300. Center(
  301. child: StreamBuilder<bool>(
  302. stream: AudioService.playbackStateStream
  303. .map((state) => state.playing)
  304. .distinct(),
  305. builder: (context, snapshot) {
  306. final playing = snapshot.data ?? false;
  307. return Container(
  308. height: 65,
  309. width: 65,
  310. child: Center(
  311. child: SizedBox(
  312. height: 59,
  313. width: 59,
  314. child: playing
  315. ? pauseButton()
  316. : playButton(),
  317. ),
  318. ),
  319. );
  320. },
  321. ),
  322. ),
  323. ],
  324. ),
  325. // Queue display/controls.
  326. StreamBuilder<QueueState>(
  327. stream: _queueStateStream,
  328. builder: (context, snapshot) {
  329. final queueState = snapshot.data;
  330. final queue = queueState?.queue ?? [];
  331. final mediaItem = queueState?.mediaItem;
  332. return (queue != null && queue.isNotEmpty)
  333. ? IconButton(
  334. icon: Icon(Icons.skip_next_rounded),
  335. iconSize: 45.0,
  336. onPressed: mediaItem == queue.last
  337. ? null
  338. : AudioService.skipToNext,
  339. )
  340. : IconButton(
  341. icon: Icon(Icons.skip_next_rounded),
  342. iconSize: 45.0,
  343. onPressed: null);
  344. },
  345. ),
  346. ],
  347. ),
  348. ),
  349. ],
  350. ],
  351. );
  352. },
  353. ),
  354. ),
  355. ),
  356. ),
  357. );
  358. }
  359.  
  360. /// A stream reporting the combined state of the current media item and its
  361. /// current position.
  362. Stream<MediaState> get _mediaStateStream =>
  363. Rx.combineLatest2<MediaItem, Duration, MediaState>(
  364. AudioService.currentMediaItemStream,
  365. AudioService.positionStream,
  366. (mediaItem, position) => MediaState(mediaItem, position));
  367.  
  368. /// A stream reporting the combined state of the current queue and the current
  369. /// media item within that queue.
  370. Stream<QueueState> get _queueStateStream =>
  371. Rx.combineLatest2<List<MediaItem>, MediaItem, QueueState>(
  372. AudioService.queueStream,
  373. AudioService.currentMediaItemStream,
  374. (queue, mediaItem) => QueueState(queue, mediaItem));
  375.  
  376. audioPlayerButton() {
  377. AudioService.start(
  378. backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
  379. androidNotificationChannelName: 'Audio Service Demo',
  380. androidNotificationColor: 0xFF2196f3,
  381. androidNotificationIcon: 'mipmap/ic_launcher',
  382. androidEnableQueue: true,
  383. );
  384. AudioService.updateQueue(queue);
  385. print('updated queue at the start');
  386. print('queue now is $queue');
  387. AudioService.setRepeatMode(AudioServiceRepeatMode.none);
  388. AudioService.setShuffleMode(AudioServiceShuffleMode.none);
  389. AudioService.play();
  390. }
  391.  
  392. FloatingActionButton playButton() => FloatingActionButton(
  393. elevation: 10,
  394. child: Icon(
  395. Icons.play_arrow_rounded,
  396. size: 40.0,
  397. color: Colors.white,
  398. ),
  399. onPressed: AudioService.play,
  400. );
  401.  
  402. FloatingActionButton pauseButton() => FloatingActionButton(
  403. elevation: 10,
  404. child: Icon(
  405. Icons.pause_rounded,
  406. color: Colors.white,
  407. size: 40.0,
  408. ),
  409. onPressed: AudioService.pause,
  410. );
  411. }
  412.  
  413. class QueueState {
  414. final List<MediaItem> queue;
  415. final MediaItem mediaItem;
  416.  
  417. QueueState(this.queue, this.mediaItem);
  418. }
  419.  
  420. class MediaState {
  421. final MediaItem mediaItem;
  422. final Duration position;
  423.  
  424. MediaState(this.mediaItem, this.position);
  425. }
  426.  
  427. class SeekBar extends StatefulWidget {
  428. final Duration duration;
  429. final Duration position;
  430. final ValueChanged<Duration> onChanged;
  431. final ValueChanged<Duration> onChangeEnd;
  432.  
  433. SeekBar({
  434. @required this.duration,
  435. @required this.position,
  436. this.onChanged,
  437. this.onChangeEnd,
  438. });
  439.  
  440. @override
  441. _SeekBarState createState() => _SeekBarState();
  442. }
  443.  
  444. class _SeekBarState extends State<SeekBar> {
  445. double _dragValue;
  446. bool _dragging = false;
  447.  
  448. @override
  449. Widget build(BuildContext context) {
  450. final value = min(_dragValue ?? widget.position?.inMilliseconds?.toDouble(),
  451. widget.duration.inMilliseconds.toDouble());
  452. if (_dragValue != null && !_dragging) {
  453. _dragValue = null;
  454. }
  455. return Column(
  456. children: [
  457. Slider(
  458. min: 0.0,
  459. max: widget.duration.inMilliseconds.toDouble(),
  460. value: value,
  461. activeColor: Theme.of(context).accentColor,
  462. inactiveColor: Theme.of(context).backgroundColor,
  463. onChanged: (value) {
  464. if (!_dragging) {
  465. _dragging = true;
  466. }
  467. setState(() {
  468. _dragValue = value;
  469. });
  470. if (widget.onChanged != null) {
  471. widget.onChanged(Duration(milliseconds: value.round()));
  472. }
  473. },
  474. onChangeEnd: (value) {
  475. if (widget.onChangeEnd != null) {
  476. widget.onChangeEnd(Duration(milliseconds: value.round()));
  477. }
  478. _dragging = false;
  479. },
  480. ),
  481. Row(
  482. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  483. children: [
  484. Padding(
  485. padding: const EdgeInsets.only(left: 20.0),
  486. child: Text(
  487. RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$')
  488. .firstMatch("$_position")
  489. ?.group(1) ??
  490. '$_position',
  491. // style: Theme.of(context).textTheme.caption,
  492. ),
  493. ),
  494. Padding(
  495. padding: const EdgeInsets.only(right: 20.0),
  496. child: Text(
  497. RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$')
  498. .firstMatch("$_duration")
  499. ?.group(1) ??
  500. '$_duration',
  501. // style: Theme.of(context).textTheme.caption,
  502. ),
  503. ),
  504. ],
  505. ),
  506. ],
  507. );
  508. }
  509.  
  510. // Duration get _remaining => widget.duration - widget.position;
  511. Duration get _position => widget.position;
  512. Duration get _duration => widget.duration;
  513. }
  514.  
  515. void _audioPlayerTaskEntrypoint() async {
  516. AudioServiceBackground.run(() => AudioPlayerTask());
  517. }
  518.  
  519. class AudioPlayerTask extends BackgroundAudioTask {
  520. AudioPlayer _player = AudioPlayer();
  521. Seeker _seeker;
  522. StreamSubscription<PlaybackEvent> _eventSubscription;
  523. String kUrl = '';
  524. String key = "38346591";
  525. String decrypt = "";
  526. String preferredQuality = '320';
  527. // List<MediaItem> queue = playlist.PlayMusic().mediaItems;
  528.  
  529. // int index = 0;
  530. int get index => _player.currentIndex == null ? 0 : _player.currentIndex;
  531. MediaItem get mediaItem => index == null ? queue[0] : queue[index];
  532.  
  533. fetchSongUrl(songId) async {
  534. print('starting fetching url');
  535. String songUrl =
  536. "https://www.jiosaavn.com/api.php?app_version=5.18.3&api_version=4&readable_version=5.18.3&v=79&_format=json&__call=song.getDetails&pids=" +
  537. songId;
  538. var res = await get(songUrl, headers: {"Accept": "application/json"});
  539. var resEdited = (res.body).split("-->");
  540. var getMain = jsonDecode(resEdited[1]);
  541. kUrl = await DesPlugin.decrypt(
  542. key, getMain[songId]["more_info"]["encrypted_media_url"]);
  543. kUrl = kUrl.replaceAll('96', '$preferredQuality');
  544. print('fetched url');
  545. return kUrl;
  546. }
  547.  
  548. @override
  549. Future<void> onStart(Map<String, dynamic> params) async {
  550. print('inside onStart of audioPlayertask');
  551. print('queue now is $queue');
  552. final session = await AudioSession.instance;
  553. await session.configure(AudioSessionConfiguration.speech());
  554. // Broadcast media item changes.
  555. print('inside onStart after configure');
  556. print('queue now is $queue');
  557. if (queue.length == 0) {
  558. print('queue is found to be null.........');
  559. }
  560. _player.currentIndexStream.listen((index) {
  561. if (index != null) AudioServiceBackground.setMediaItem(queue[index]);
  562. });
  563. // Propagate all events from the audio player to AudioService clients.
  564. _eventSubscription = _player.playbackEventStream.listen((event) {
  565. _broadcastState();
  566. });
  567. // Special processing for state transitions.
  568. _player.processingStateStream.listen((state) {
  569. switch (state) {
  570. case ProcessingState.completed:
  571. AudioService.currentMediaItem != queue.last
  572. ? AudioService.skipToNext()
  573. : AudioService.stop();
  574. break;
  575. case ProcessingState.ready:
  576. break;
  577. default:
  578. break;
  579. }
  580. });
  581.  
  582. // Load and broadcast the queue
  583. print('queue is');
  584. print(queue);
  585. print('Index is $index');
  586. print('MediaItem is');
  587. print(queue[index]);
  588. try {
  589. if (queue[index].extras == null) {
  590. queue[index] = queue[index].copyWith(extras: {
  591. 'URL': await fetchSongUrl(queue[index].id),
  592. });
  593. }
  594.  
  595. await AudioServiceBackground.setQueue(queue);
  596. await _player.setUrl(queue[index].extras['URL']);
  597. onPlay();
  598. } catch (e) {
  599. print("Error: $e");
  600. onStop();
  601. }
  602. }
  603.  
  604. @override
  605. Future<void> onSkipToQueueItem(String mediaId) async {
  606. // Then default implementations of onSkipToNext and onSkipToPrevious will
  607. // delegate to this method.
  608. final newIndex = queue.indexWhere((item) => item.id == mediaId);
  609. if (newIndex == -1) return;
  610. _player.pause();
  611. if (queue[newIndex].extras == null) {
  612. queue[newIndex] = queue[newIndex].copyWith(extras: {
  613. 'URL': await fetchSongUrl(queue[newIndex].id),
  614. });
  615. await AudioServiceBackground.setQueue(queue);
  616. // AudioService.updateQueue(queue);
  617. }
  618. await _player.setUrl(queue[newIndex].extras['URL']);
  619. _player.play();
  620. await AudioServiceBackground.setMediaItem(queue[newIndex]);
  621. }
  622.  
  623. @override
  624. Future<void> onUpdateQueue(List<MediaItem> queue) {
  625. AudioServiceBackground.setQueue(queue = queue);
  626. return super.onUpdateQueue(queue);
  627. }
  628.  
  629. @override
  630. Future<void> onPlay() => _player.play();
  631.  
  632. @override
  633. Future<void> onPause() => _player.pause();
  634.  
  635. @override
  636. Future<void> onSeekTo(Duration position) => _player.seek(position);
  637.  
  638. @override
  639. Future<void> onFastForward() => _seekRelative(fastForwardInterval);
  640.  
  641. @override
  642. Future<void> onRewind() => _seekRelative(-rewindInterval);
  643.  
  644. @override
  645. Future<void> onSeekForward(bool begin) async => _seekContinuously(begin, 1);
  646.  
  647. @override
  648. Future<void> onSeekBackward(bool begin) async => _seekContinuously(begin, -1);
  649.  
  650. @override
  651. Future<void> onStop() async {
  652. await _player.dispose();
  653. _eventSubscription.cancel();
  654. // It is important to wait for this state to be broadcast before we shut
  655. // down the task. If we don't, the background task will be destroyed before
  656. // the message gets sent to the UI.
  657. await _broadcastState();
  658. // Shut down this task
  659. await super.onStop();
  660. }
  661.  
  662. /// Jumps away from the current position by [offset].
  663. Future<void> _seekRelative(Duration offset) async {
  664. var newPosition = _player.position + offset;
  665. // Make sure we don't jump out of bounds.
  666. if (newPosition < Duration.zero) newPosition = Duration.zero;
  667. if (newPosition > mediaItem.duration) newPosition = mediaItem.duration;
  668. // Perform the jump via a seek.
  669. await _player.seek(newPosition);
  670. }
  671.  
  672. /// Begins or stops a continuous seek in [direction]. After it begins it will
  673. /// continue seeking forward or backward by 10 seconds within the audio, at
  674. /// intervals of 1 second in app time.
  675. void _seekContinuously(bool begin, int direction) {
  676. _seeker?.stop();
  677. if (begin) {
  678. _seeker = Seeker(_player, Duration(seconds: 10 * direction),
  679. Duration(seconds: 1), mediaItem)
  680. ..start();
  681. }
  682. }
  683.  
  684. /// Broadcasts the current state to all clients.
  685. Future<void> _broadcastState() async {
  686. await AudioServiceBackground.setState(
  687. controls: [
  688. MediaControl.skipToPrevious,
  689. if (_player.playing) MediaControl.pause else MediaControl.play,
  690. MediaControl.stop,
  691. MediaControl.skipToNext,
  692. ],
  693. systemActions: [
  694. MediaAction.seekTo,
  695. MediaAction.seekForward,
  696. MediaAction.seekBackward,
  697. ],
  698. androidCompactActions: [0, 1, 3],
  699. processingState: _getProcessingState(),
  700. playing: _player.playing,
  701. position: _player.position,
  702. bufferedPosition: _player.bufferedPosition,
  703. speed: _player.speed,
  704. );
  705. }
  706.  
  707. /// Maps just_audio's processing state into into audio_service's playing
  708. /// state. If we are in the middle of a skip, we use [_skipState] instead.
  709. AudioProcessingState _getProcessingState() {
  710. switch (_player.processingState) {
  711. case ProcessingState.idle:
  712. return AudioProcessingState.stopped;
  713. case ProcessingState.loading:
  714. return AudioProcessingState.connecting;
  715. case ProcessingState.buffering:
  716. return AudioProcessingState.buffering;
  717. case ProcessingState.ready:
  718. return AudioProcessingState.ready;
  719. case ProcessingState.completed:
  720. return AudioProcessingState.completed;
  721. default:
  722. throw Exception("Invalid state: ${_player.processingState}");
  723. }
  724. }
  725. }
  726.  
  727. /// An object that performs interruptable sleep.
  728. class Sleeper {
  729. Completer _blockingCompleter;
  730.  
  731. /// Sleep for a duration. If sleep is interrupted, a
  732. /// [SleeperInterruptedException] will be thrown.
  733. Future<void> sleep([Duration duration]) async {
  734. _blockingCompleter = Completer();
  735. if (duration != null) {
  736. await Future.any([Future.delayed(duration), _blockingCompleter.future]);
  737. } else {
  738. await _blockingCompleter.future;
  739. }
  740. final interrupted = _blockingCompleter.isCompleted;
  741. _blockingCompleter = null;
  742. if (interrupted) {
  743. throw SleeperInterruptedException();
  744. }
  745. }
  746.  
  747. /// Interrupt any sleep that's underway.
  748. void interrupt() {
  749. if (_blockingCompleter?.isCompleted == false) {
  750. _blockingCompleter.complete();
  751. }
  752. }
  753. }
  754.  
  755. class SleeperInterruptedException {}
  756.  
  757. class Seeker {
  758. final AudioPlayer player;
  759. final Duration positionInterval;
  760. final Duration stepInterval;
  761. final MediaItem mediaItem;
  762. bool _running = false;
  763.  
  764. Seeker(
  765. this.player,
  766. this.positionInterval,
  767. this.stepInterval,
  768. this.mediaItem,
  769. );
  770.  
  771. start() async {
  772. _running = true;
  773. while (_running) {
  774. Duration newPosition = player.position + positionInterval;
  775. if (newPosition < Duration.zero) newPosition = Duration.zero;
  776. if (newPosition > mediaItem.duration) newPosition = mediaItem.duration;
  777. player.seek(newPosition);
  778. await Future.delayed(stepInterval);
  779. }
  780. }
  781.  
  782. stop() {
  783. _running = false;
  784. }
  785. }
  786.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement