Search code examples
c#win-universal-appwindows-10-mobilebackground-audio

BackgroundAudio not work in windows 10 mobile


I have a list with words(about 200), which I want to play in player. This words reproduce from SpeechSynthesisStream. When I run it's in my PC, all words play perfect, but in my phone play just one word and don't move to next.

 #region Helper methods

    string GetCurrentTrackId()
    {
        if (playbackList == null)
            return null;

        return GetTrackId(playbackList.CurrentItem);
    }

    string GetTrackId(MediaPlaybackItem item)
    {
        if (item == null)
            return null; // no track playing

        return item.Source.CustomProperties[TitleKey] as string;
    }
    #endregion

    #region IBackgroundTask and IBackgroundTaskInstance Interface Members and handlers
    /// <summary>
    /// The Run method is the entry point of a background task. 
    /// </summary>
    /// <param name="taskInstance"></param>
    public void Run(IBackgroundTaskInstance taskInstance)
    {
        Debug.WriteLine("Background Audio Task " + taskInstance.Task.Name + " starting...");

        // Initialize SystemMediaTransportControls (SMTC) for integration with
        // the Universal Volume Control (UVC).
        //
        // The UI for the UVC must update even when the foreground process has been terminated
        // and therefore the SMTC is configured and updated from the background task.
        smtc = BackgroundMediaPlayer.Current.SystemMediaTransportControls;
        smtc.ButtonPressed += smtc_ButtonPressed;
        smtc.PropertyChanged += smtc_PropertyChanged;
        smtc.IsEnabled = true;
        smtc.IsPauseEnabled = true;
        smtc.IsPlayEnabled = true;
        smtc.IsNextEnabled = true;
        smtc.IsPreviousEnabled = true;

        // Read persisted state of foreground app
        var value = ApplicationSettingsHelper.ReadResetSettingsValue(ApplicationSettingsConstants.AppState);
        if (value == null)
            foregroundAppState = AppState.Unknown;
        else
            foregroundAppState = EnumHelper.Parse<AppState>(value.ToString());

        // Add handlers for MediaPlayer
        BackgroundMediaPlayer.Current.CurrentStateChanged += Current_CurrentStateChanged;

        // Initialize message channel 
        BackgroundMediaPlayer.MessageReceivedFromForeground += BackgroundMediaPlayer_MessageReceivedFromForeground;

        // Send information to foreground that background task has been started if app is active
        if (foregroundAppState != AppState.Suspended)
            MessageService.SendMessageToForeground(new BackgroundAudioTaskStartedMessage());

        ApplicationSettingsHelper.SaveSettingsValue(ApplicationSettingsConstants.BackgroundTaskState, BackgroundTaskState.Running.ToString());

        deferral = taskInstance.GetDeferral(); // This must be retrieved prior to subscribing to events below which use it

        // Mark the background task as started to unblock SMTC Play operation (see related WaitOne on this signal)
        backgroundTaskStarted.Set();

        // Associate a cancellation and completed handlers with the background task.
        taskInstance.Task.Completed += TaskCompleted;
        taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled); // event may raise immediately before continung thread excecution so must be at the end
    }

    /// <summary>
    /// Indicate that the background task is completed.
    /// </summary>       
    void TaskCompleted(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
    {
        Debug.WriteLine("MyBackgroundAudioTask " + sender.TaskId + " Completed...");
        deferral.Complete();
    }

    /// <summary>
    /// Handles background task cancellation. Task cancellation happens due to:
    /// 1. Another Media app comes into foreground and starts playing music 
    /// 2. Resource pressure. Your task is consuming more CPU and memory than allowed.
    /// In either case, save state so that if foreground app resumes it can know where to start.
    /// </summary>
    private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
    {
        // You get some time here to save your state before process and resources are reclaimed
        Debug.WriteLine("MyBackgroundAudioTask " + sender.Task.TaskId + " Cancel Requested...");
        try
        {
            // immediately set not running
            backgroundTaskStarted.Reset();

            // save state
            ApplicationSettingsHelper.SaveSettingsValue(ApplicationSettingsConstants.TrackId, GetCurrentTrackId() == null ? null : GetCurrentTrackId().ToString());
            ApplicationSettingsHelper.SaveSettingsValue(ApplicationSettingsConstants.Position, BackgroundMediaPlayer.Current.Position.ToString());
            ApplicationSettingsHelper.SaveSettingsValue(ApplicationSettingsConstants.BackgroundTaskState, BackgroundTaskState.Canceled.ToString());
            ApplicationSettingsHelper.SaveSettingsValue(ApplicationSettingsConstants.AppState, Enum.GetName(typeof(AppState), foregroundAppState));

            // unsubscribe from list changes
            if (playbackList != null)
            {
                playbackList.CurrentItemChanged -= PlaybackList_CurrentItemChanged;
                playbackList = null;
            }

            // unsubscribe event handlers
            BackgroundMediaPlayer.MessageReceivedFromForeground -= BackgroundMediaPlayer_MessageReceivedFromForeground;
            smtc.ButtonPressed -= smtc_ButtonPressed;
            smtc.PropertyChanged -= smtc_PropertyChanged;

            BackgroundMediaPlayer.Shutdown(); // shutdown media pipeline
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }
        deferral.Complete(); // signals task completion. 
        Debug.WriteLine("MyBackgroundAudioTask Cancel complete...");
    }
    #endregion

    #region SysteMediaTransportControls related functions and handlers
    /// <summary>
    /// Update Universal Volume Control (UVC) using SystemMediaTransPortControl APIs
    /// </summary>
    private void UpdateUVCOnNewTrack(MediaPlaybackItem item)
    {
        if (item == null)
        {
            smtc.PlaybackStatus = MediaPlaybackStatus.Stopped;
            smtc.DisplayUpdater.MusicProperties.Title = string.Empty;
            smtc.DisplayUpdater.Update();
            return;
        }

        smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
        smtc.DisplayUpdater.Type = MediaPlaybackType.Music;
        smtc.DisplayUpdater.MusicProperties.Title = item.Source.CustomProperties[TitleKey] as string;

        var albumArtUri = item.Source.CustomProperties[AlbumArtKey] as Uri;
        if (albumArtUri != null)
            smtc.DisplayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri(albumArtUri);
        else
            smtc.DisplayUpdater.Thumbnail = null;

        smtc.DisplayUpdater.Update();
    }

    /// <summary>
    /// Fires when any SystemMediaTransportControl property is changed by system or user
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    void smtc_PropertyChanged(SystemMediaTransportControls sender, SystemMediaTransportControlsPropertyChangedEventArgs args)
    {
        // If soundlevel turns to muted, app can choose to pause the music
    }

    /// <summary>
    /// This function controls the button events from UVC.
    /// This code if not run in background process, will not be able to handle button pressed events when app is suspended.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private void smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
    {
        switch (args.Button)
        {
            case SystemMediaTransportControlsButton.Play:
                Debug.WriteLine("UVC play button pressed");

                // When the background task has been suspended and the SMTC
                // starts it again asynchronously, some time is needed to let
                // the task startup process in Run() complete.

                // Wait for task to start. 
                // Once started, this stays signaled until shutdown so it won't wait
                // again unless it needs to.
                bool result = backgroundTaskStarted.WaitOne(5000);
                if (!result)
                    throw new Exception("Background Task didnt initialize in time");

                StartPlayback();
                break;
            case SystemMediaTransportControlsButton.Pause:
                Debug.WriteLine("UVC pause button pressed");
                try
                {
                    BackgroundMediaPlayer.Current.Pause();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.ToString());
                }
                break;
            case SystemMediaTransportControlsButton.Next:
                Debug.WriteLine("UVC next button pressed");
                SkipToNext();
                break;
            case SystemMediaTransportControlsButton.Previous:
                Debug.WriteLine("UVC previous button pressed");
                SkipToPrevious();
                break;
        }
    }



    #endregion

    #region Playlist management functions and handlers
    /// <summary>
    /// Start playlist and change UVC state
    /// </summary>
    private void StartPlayback()
    {
        try
        {
            // If playback was already started once we can just resume playing.
            if (!playbackStartedPreviously)
            {
                playbackStartedPreviously = true;

                // If the task was cancelled we would have saved the current track and its position. We will try playback from there.
                var currentTrackId = ApplicationSettingsHelper.ReadResetSettingsValue(ApplicationSettingsConstants.TrackId);
                var currentTrackPosition = ApplicationSettingsHelper.ReadResetSettingsValue(ApplicationSettingsConstants.Position);
                if (currentTrackId != null)
                {
                    // Find the index of the item by name
                    var index = playbackList.Items.ToList().FindIndex(item =>
                        GetTrackId(item).ToString() == (string)currentTrackId);

                    if (currentTrackPosition == null)
                    {
                        // Play from start if we dont have position
                        Debug.WriteLine("StartPlayback: Switching to track " + index);
                        playbackList.MoveTo((uint)index);

                        // Begin playing
                        BackgroundMediaPlayer.Current.Play();
                    }
                    else
                    {
                        // Play from exact position otherwise
                        TypedEventHandler<MediaPlaybackList, CurrentMediaPlaybackItemChangedEventArgs> handler = null;
                        handler = (MediaPlaybackList list, CurrentMediaPlaybackItemChangedEventArgs args) =>
                        {
                            if (args.NewItem == playbackList.Items[index])
                            {
                                // Unsubscribe because this only had to run once for this item
                                playbackList.CurrentItemChanged -= handler;

                                // Set position
                                var position = TimeSpan.Parse((string)currentTrackPosition);
                                Debug.WriteLine("StartPlayback: Setting Position " + position);
                                BackgroundMediaPlayer.Current.Position = position;

                                // Begin playing
                                BackgroundMediaPlayer.Current.Play();
                            }
                        };
                        playbackList.CurrentItemChanged += handler;

                        // Switch to the track which will trigger an item changed event
                        Debug.WriteLine("StartPlayback: Switching to track " + index);
                        playbackList.MoveTo((uint)index);
                    }
                }
                else
                {
                    // Begin playing
                    BackgroundMediaPlayer.Current.Play();
                }
            }
            else
            {
                // Begin playing
                BackgroundMediaPlayer.Current.Play();
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }
    }

    /// <summary>
    /// Raised when playlist changes to a new track
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    void PlaybackList_CurrentItemChanged(MediaPlaybackList sender, CurrentMediaPlaybackItemChangedEventArgs args)
    {
        // Get the new item
        var item = args.NewItem;
        Debug.WriteLine("PlaybackList_CurrentItemChanged: " + (item == null ? "null" : GetTrackId(item).ToString()));

        // Update the system view
        UpdateUVCOnNewTrack(item);

        // Get the current track
        string currentTrackId = null;
        if (item != null)
            currentTrackId = item.Source.CustomProperties[TrackIdKey] as string;

        // Notify foreground of change or persist for later
        if (foregroundAppState == AppState.Active)
            MessageService.SendMessageToForeground(new TrackChangedMessage(currentTrackId));
        else
            ApplicationSettingsHelper.SaveSettingsValue(TrackIdKey, currentTrackId == null ? null : currentTrackId.ToString());
    }

    /// <summary>
    /// Skip track and update UVC via SMTC
    /// </summary>
    private void SkipToPrevious()
    {
        smtc.PlaybackStatus = MediaPlaybackStatus.Changing;
        playbackList.MovePrevious();
    }

    /// <summary>
    /// Skip track and update UVC via SMTC
    /// </summary>
    private void SkipToNext()
    {
        smtc.PlaybackStatus = MediaPlaybackStatus.Changing;
        playbackList.MoveNext();
    }
    #endregion

    #region Background Media Player Handlers
    void Current_CurrentStateChanged(MediaPlayer sender, object args)
    {
        if (sender.CurrentState == MediaPlayerState.Playing)
        {
            smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
        }
        else if (sender.CurrentState == MediaPlayerState.Paused)
        {
            smtc.PlaybackStatus = MediaPlaybackStatus.Paused;
        }
        else if (sender.CurrentState == MediaPlayerState.Closed)
        {
            smtc.PlaybackStatus = MediaPlaybackStatus.Closed;
        }
    }

    /// <summary>
    /// Raised when a message is recieved from the foreground app
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void BackgroundMediaPlayer_MessageReceivedFromForeground(object sender, MediaPlayerDataReceivedEventArgs e)
    {
        AppSuspendedMessage appSuspendedMessage;
        if (MessageService.TryParseMessage(e.Data, out appSuspendedMessage))
        {
            Debug.WriteLine("App suspending"); // App is suspended, you can save your task state at this point
            foregroundAppState = AppState.Suspended;
            var currentTrackId = GetCurrentTrackId();
            ApplicationSettingsHelper.SaveSettingsValue(ApplicationSettingsConstants.TrackId, currentTrackId == null ? null : currentTrackId.ToString());
            return;
        }

        AppResumedMessage appResumedMessage;
        if (MessageService.TryParseMessage(e.Data, out appResumedMessage))
        {
            Debug.WriteLine("App resuming"); // App is resumed, now subscribe to message channel
            foregroundAppState = AppState.Active;
            return;
        }

        StartPlaybackMessage startPlaybackMessage;
        if (MessageService.TryParseMessage(e.Data, out startPlaybackMessage))
        {
            //Foreground App process has signalled that it is ready for playback
            Debug.WriteLine("Starting Playback");
            StartPlayback();
            return;
        }

        SkipNextMessage skipNextMessage;
        if (MessageService.TryParseMessage(e.Data, out skipNextMessage))
        {
            // User has chosen to skip track from app context.
            Debug.WriteLine("Skipping to next");
            SkipToNext();
            return;
        }

        SkipPreviousMessage skipPreviousMessage;
        if (MessageService.TryParseMessage(e.Data, out skipPreviousMessage))
        {
            // User has chosen to skip track from app context.
            Debug.WriteLine("Skipping to previous");
            SkipToPrevious();
            return;
        }

        TrackChangedMessage trackChangedMessage;
        if (MessageService.TryParseMessage(e.Data, out trackChangedMessage))
        {
            var index = playbackList.Items.ToList().FindIndex(i => (string)i.Source.CustomProperties[TrackIdKey] == trackChangedMessage.TrackId);
            Debug.WriteLine("Skipping to track " + index);
            smtc.PlaybackStatus = MediaPlaybackStatus.Changing;
            playbackList.MoveTo((uint)index);
            return;
        }

        UpdatePlaylistMessage updatePlaylistMessage;
        if (MessageService.TryParseMessage(e.Data, out updatePlaylistMessage))
        {
            CreatePlaybackList(updatePlaylistMessage.Songs);
            return;
        }
    }
    /// <summary>
    /// Create a playback list from the list of songs received from the foreground app.
    /// </summary>
    /// <param name="songs"></param>
    /// 
    void CreatePlaybackList(IEnumerable<SongModel> songs)
    {
        // Make a new list and enable looping
        playbackList = new MediaPlaybackList();
        playbackList.AutoRepeatEnabled = true;
         // Add playback items to the list
            foreach (var song in songs)
            {
                Task.Run(async () => {
                SpeechSynthesisStream synthesisStream = await GetStream(song.Verbs);
                var source = MediaSource.CreateFromStream(synthesisStream,synthesisStream.GetType().ToString());
                source.CustomProperties[TrackIdKey] = song.ID;
                source.CustomProperties[TitleKey] = song.Title;
                source.CustomProperties[AlbumArtKey] = song.AlbumArtUri;
                playbackList.Items.Add(new MediaPlaybackItem(source));
                });
            }

        // Don't auto start
        BackgroundMediaPlayer.Current.AutoPlay = false;

        // Assign the list to the player
        BackgroundMediaPlayer.Current.Source = playbackList;

        // Add handler for future playlist item changes
        playbackList.CurrentItemChanged += PlaybackList_CurrentItemChanged;
    }

    #endregion


    private async Task<SpeechSynthesisStream> GetStream(string verb)
    {
        SpeechSynthesisStream synthesisStream;
        SpeechSynthesizer synthesizer = new SpeechSynthesizer();
        var voices = SpeechSynthesizer.AllVoices;
        bool speechFound = false;
        foreach (VoiceInformation voice in voices.OrderBy(p => p.Language))
        {
            if (voice.Language == "de-DE")
            {
                synthesizer.Voice = voice;
                speechFound = true;
            }
        }
        if (speechFound)
        {
            synthesisStream = await synthesizer.SynthesizeTextToStreamAsync(verb);
            return synthesisStream;
        }
        return null;
    }

I changed just this method

 void CreatePlaybackList(IEnumerable<SongModel> songs)
    {
        // Make a new list and enable looping
        playbackList = new MediaPlaybackList();
        playbackList.AutoRepeatEnabled = true;
         // Add playback items to the list
            foreach (var song in songs)
            {
                Task.Run(async () => {
                SpeechSynthesisStream synthesisStream = await GetStream(song.Verbs);
                var source = MediaSource.CreateFromStream(synthesisStream,synthesisStream.GetType().ToString());
                source.CustomProperties[TrackIdKey] = song.ID;
                source.CustomProperties[TitleKey] = song.Title;
                source.CustomProperties[AlbumArtKey] = song.AlbumArtUri;
                playbackList.Items.Add(new MediaPlaybackItem(source));
                });
            }

        // Don't auto start
        BackgroundMediaPlayer.Current.AutoPlay = false;

        // Assign the list to the player
        BackgroundMediaPlayer.Current.Source = playbackList;

        // Add handler for future playlist item changes
        playbackList.CurrentItemChanged += PlaybackList_CurrentItemChanged;
    }

Maybe problem is in SpeechSynthesisStream? And it's spend a lot of memory, because playlist have more 200 songs?


Solution

  • I think that you should create your own playlist class where you will be able to fully control your operations. I did that and it work's perfectly in my app.