Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"onSetRating" not getting called on pressing notification action button #376

Open
xsahil03x opened this issue Jun 25, 2020 · 6 comments
Open
Assignees
Labels
1 backlog question Further information is requested

Comments

@xsahil03x
Copy link

Hey, @ryanheise thanks for creating and maintaining this awesome package. I am currently using it in one of my apps and it is working out great for me. I am just stock on adding the last workflow
i.e: Adding a like/dislike action in my notification.

I tried adding new actions in the notification and they both are getting visible.

  static MediaControl like = MediaControl(
    androidIcon: 'drawable/ic_action_like',
    label: 'Like',
    action: MediaAction.setRating,
  );

  static MediaControl dislike = MediaControl(
    androidIcon: 'drawable/ic_action_dislike',
    label: 'Dislike',
    action: MediaAction.setRating,
  );

But the problem here is the callback onSetRating is not getting called when clicking above
two media controls.

  @override
  void onSetRating(Rating rating, Map<dynamic, dynamic> extras) {
    // Not getting called while pressing notification action with
    // type MediaAction.setRating
  }

Can you please guide me on how to make it work.

@xsahil03x xsahil03x added 1 backlog bug Something isn't working labels Jun 25, 2020
@ryanheise
Copy link
Owner

I assume you're asking a question and not filing a bug report? I'll change your issue type. (Note that I tend to give a higher priority to bug reports, though).

@ryanheise ryanheise added question Further information is requested and removed bug Something isn't working labels Jun 26, 2020
@chrisheib
Copy link

Hey there, this is quite the zombie, but I fail even earlier: I can't get the rating buttons to show up at all!

class MyAudioHandler extends BaseAudioHandler {
  final _player = initAudioPlayer();
  final _config = getConfig();

  MyAudioHandler() {    
    ratingStyle.add(RatingStyle.thumbUpDown);
    _player.playbackEventStream.listen((PlaybackEvent event) {
      // print("Playbackeventstream event: ${event.toString()}");
      final playing = _player.playing;
      playbackState.add(playbackState.value.copyWith(
        controls: [
          if (playing) MediaControl.pause else MediaControl.play,
          MediaControl.skipToNext,
          like,
          dislike
        ],
        systemActions: const {
          MediaAction.seek,
          // MediaAction.setRating
        },
        androidCompactActionIndices: const [
          0,
        ],
        processingState: const {
          ProcessingState.idle: AudioProcessingState.idle,
          ProcessingState.loading: AudioProcessingState.loading,
          ProcessingState.buffering: AudioProcessingState.buffering,
          ProcessingState.ready: AudioProcessingState.ready,
          ProcessingState.completed: AudioProcessingState.completed,
        }[_player.processingState]!,
        playing: playing,
        updatePosition: _player.position,
      ));
  }
  @override
  Future<void> play() => setPlay();

  @override
  Future<void> pause() => setPause();

  @override
  Future<void> seek(Duration position) => _player.seek(position);

  @override
  Future<void> skipToNext() => setSkip();

  @override
  Future<void> setRating(Rating rating, [Map<String, dynamic>? extras]) async {
    print("Rating: ${rating.toString()}, extras: ${extras.toString()}");
  }

  @override
  Future customAction(String name, [Map<String, dynamic>? extras]) async {
    if (name == 'dispose') {
      await _player.dispose();
      super.stop();
    }
  }

  static MediaControl like = MediaControl(
    androidIcon: 'drawable/ic_action_like',
    label: 'Like',
    action: MediaAction.setRating,
  );

  static MediaControl dislike = MediaControl(
    androidIcon: 'drawable/ic_action_dislike',
    label: 'Dislike',
    action: MediaAction.setRating,
  );
}

Media Item Generation:

  MediaItem toMediaItem() {
    return MediaItem(
      id: id.toString(),
      album: album,
      title: title.isEmpty ? filename : title,
      artist: artist,
      rating: const Rating.newThumbRating(false),
      extras: {"rating": rating},
    );
  }

Any Idea on why this is not working?

@chrisheib
Copy link

So far I found out that for the icons to appear I actually need to include them in my drawables (duh).
Unfortunately now I'm having the same problem as @xsahil03x - the setRating hook just never gets called.
How are ratings supposed to work? Once I get it running I'll get a PR on it's way to improve documentation for rating 😅

@ryanheise
Copy link
Owner

Hi @chrisheib , I'd be happy to accept a PR if it needs a patch. I'll also tag @hacker1024 who contributed the original ratings about 4 years ago (wow, that's quite a long project history :-) ). Maybe there was some secret sauce to get it working.

@chrisheib
Copy link

Hey Ryan, unfortunately I need to surrender. I spent waaaay to much time in the last couple of days to try to understand the structure, but as this is the first (and maybe only) mobile app I will write, it didn't seem a worthy investment to dig deeper.

I managed to get the functionality working as I need it for my app with abusing two working events for my purpose, giving them custom icons.

For now it looks like this:
grafik

I did find a few interesting points which someone might look into further.

The main problem seems to be the function PendingIntent buildMediaButtonPendingIntent(long action) in audioservice/AudioService.java.

In there is a attempt to convert from the action ID (128 for setRating) to a MediaKey-string. Unfortunately there are no MediaKeys for SetRating, so at this point the function returns null early, making the buttons in the notification 'intentless' and therefore nonfunctioning.

When changing the function to something like this:

    PendingIntent buildMediaButtonPendingIntent(long action) {
        int keyCode = toKeyCode(action);
        Log.i("MYDEB buildMediaButtonPendingIntent", "action: " + action + ", keycode: " + keyCode);
        if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
            Intent intent = new Intent(this, MediaButtonReceiver.class);
            intent.setAction(Intent.ACTION_MEDIA_BUTTON);
            intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
            int flags = 0;
            if (Build.VERSION.SDK_INT >= 23) {
                flags |= PendingIntent.FLAG_IMMUTABLE;
            }
            return PendingIntent.getBroadcast(this, keyCode, intent, flags);
        } else {
            Intent intent = new Intent(this, MediaButtonReceiver.class);
            intent.setAction("com.ryanheise.audioservice." + action);
            int flags = 0;
            if (Build.VERSION.SDK_INT >= 23) {
                flags |= PendingIntent.FLAG_IMMUTABLE;
            }
            return PendingIntent.getBroadcast(this, keyCode, intent, flags);
        }
    }

We DO get a callback, but the permission system of android blocks the call. We would need to set up another RECEIVER- and/or SERVICE-Element for every ID in the AndroidManifest.xml and implement the callback handler as done in MediaButtonReceiver.java. This would probably allow for arbitrary events to be handled. Seems too much of a hassle for me tho, especially as I think this will be super hard to upstream.

My hacky workaround:

  static MediaControl like = const MediaControl(
    androidIcon: 'drawable/thumbs_up',
    label: 'Like',
    action: MediaAction.skipToPrevious,
  );

  static MediaControl dislike = const MediaControl(
    androidIcon: 'drawable/thumbs_down',
    label: 'Dislike',
    action: MediaAction.stop,
  );

  @override
  Future<void> skipToPrevious() async {
    setUpvote();
    print("skipToPrevious -> upvote");
  }

  @override
  Future<void> stop() async {
    setDownvote();
    print("stop -> downvote");
  }

I even added a few different MediaControls to display the current rating of the song (1-7 stars in my app):

  static MediaControl oneStar = const MediaControl(
    androidIcon: 'drawable/onestar',
    label: 'Dislike',
    action: MediaAction.setRating,
  );

  static MediaControl twoStar = const MediaControl(
    androidIcon: 'drawable/twostar',
    label: 'Dislike',
    action: MediaAction.setRating,
  );

  static MediaControl threeStar = const MediaControl(
    androidIcon: 'drawable/threestar',
    label: 'Dislike',
    action: MediaAction.setRating,
  );

.....

and setting them every time the rating changes:

  void setControlsFromRating(int rating) {
    final playing = _player.playing;
    var pb = playbackState.value.copyWith(controls: [
      if (playing) MediaControl.pause else MediaControl.play,
      MediaControl.skipToNext,
      like,
      dislike,
    ]);
    var ratingC = ratingToControl(rating);
    if (ratingC != null) {
      pb.controls.add(ratingC);
    }
    playbackState.add(pb);
  }

  MediaControl? ratingToControl(int rating) {
    switch (rating) {
      case 1:
        return oneStar;
      case 2:
        return twoStar;
      case 3:
        return threeStar;
      case 4:
        return fourStar;
      case 5:
        return fiveStar;
      case 6:
        return sixStar;
      case 7:
        return sevenStar;
      default:
        return null;
    }
  }

@ryanheise
Copy link
Owner

That's fair enough as a short term workaround. Another related feature request is #633 which is to support custom actions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1 backlog question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants