Search Unity

Audio Multiplayer App Background Music Syncing

Discussion in 'Audio & Video' started by Spieljaeger, Oct 25, 2019.

  1. Spieljaeger

    Spieljaeger

    Joined:
    Jan 27, 2018
    Posts:
    3
    I'm developing a multiplayer VR app in Unity with Photon. I want to have background music which is in sync for all players. But what's happening with what I've tried so far is that the music starts from the beginning for each player as they enter the game. So for example if the first player to enter is at 30" into the music (having started at 0"), the music will continue playing from 30" for the first player but from 0" for the second player. Instead I want the music to be playing at 30" (or whatever the time is for the first player) when the second player (and third and fourth, etc.) enters. Can anyone assist?
     
    Last edited: Oct 25, 2019
  2. JLF

    JLF

    Joined:
    Feb 25, 2013
    Posts:
    139
    Hi, so there are a couple of methods to sync up music timing and to change the position of an Audio Clip, but this way is probably the most accurate as it uses PlayScheduled. For more info, I wrote a long article about PlayScheduled here.

    The example below has an array of four local Audio Sources. The first starts and the start time is logged from the sample position. When other players enter, the time since the first start is calculated and turned back into a sample position and the clip is started at that position.

    What should happen is every Audio Source will join in exact time with the original clip using the double start time of the first Audio Clip as a reference point. I've only tested this in a single Scene with local Audio Sources. I think that you could do this over a network, but I'm not aware of what options you have to sync it. The dspTime for each machine will be different but this only really matters when calculating how much of the Clip has played. This should still work where, in the example below, hostMachineTime and firstStartTime refer to the dspTime values for Player 1's machine.

    A couple of other things to point out.

    • This method uses modulo to strip the time since start value down to progress through the clip only. This is to avoid setting a value larger than the length of the clip if you have a looping clip that's looped several times already.
    • There's a 1 second delay added to the start time to give PlayScheduled a chance to get ready to play. You can probably reduce this value if you need to but be aware there should be some sort of buffer to keep it working under load.
    • You might want to fade in the Audio Source when it starts. I've not added that in but I've got an article on it here that you might find useful.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MusicController : MonoBehaviour
    6. {
    7.     public AudioSource[] player;
    8.     public double firstStartTime;
    9.  
    10.     public double clipLength;
    11.     public int sampleRate;
    12.  
    13.     private void Start()
    14.     {
    15.         clipLength = GetClipLength();
    16.         StartMusic();
    17.     }
    18.  
    19.     double GetClipLength()
    20.     {
    21.         sampleRate = player[0].clip.frequency;
    22.         double length = (double)player[0].clip.samples / sampleRate;
    23.         return length;
    24.     }
    25.  
    26.     void StartMusic()
    27.     {
    28.         firstStartTime = AudioSettings.dspTime + 1;
    29.         player[0].PlayScheduled(firstStartTime);
    30.     }
    31.  
    32.     public void StartMusicWithDelay(int playerNumber)
    33.     {
    34.         if (!player[playerNumber].isPlaying)
    35.         {
    36.             //This will need to be the AudioSettings.dspTime of the host machine
    37.             double hostMachineTime = AudioSettings.dspTime + 1;
    38.             //This is the time on the Player's machine that the clip will start.
    39.             double localStartTime = AudioSettings.dspTime + 1;
    40.             double timeSinceStart = (hostMachineTime - firstStartTime) % clipLength;
    41.             int samplePosition = (int)(sampleRate * timeSinceStart);
    42.             player[playerNumber].timeSamples = samplePosition;
    43.             player[playerNumber].PlayScheduled(localStartTime);
    44.         }
    45.         else
    46.         {
    47.             Debug.Log("Music is already playing for that player");
    48.         }
    49.     }
    50.  
    51. }
    52.  
     
  3. Spieljaeger

    Spieljaeger

    Joined:
    Jan 27, 2018
    Posts:
    3
    John's guidance and code is very helpful. Unfortunately, I've been unsuccessful in adapting his code to my multiplayer app. The script I came up with is copied below, and what happens (and doesn't happen) is shown in comments at the bottom of the script. Some aspects of the code seem to be working, but no sound is heard by the second player to enter.

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Photon.Pun;
    using TMPro;
    using Photon.Realtime;

    public class DWM_PlayerSetup : MonoBehaviourPunCallbacks
    {

    [SerializeField] GameObject centerEyeAnchor;

    [SerializeField] GameObject bulletStartPos;

    [SerializeField] TextMeshProUGUI playerNameText;

    [SerializeField] GameObject audioSourceObject;

    public double hostDSP;

    // Per John French MusicController script:
    public AudioSource audioSource;
    public double firstStartTime;
    public double clipLength;
    public double sampleRate;

    void Start()
    {

    // Per John French MusicController script:
    clipLength = GetClipLength();

    if (PhotonNetwork.CurrentRoom.PlayerCount == 1)
    StartMusic();



    if (photonView.IsMine == false)
    {
    transform.GetComponent<DWM_FPControllerVRAllPurpose3DII>().enabled = false;
    bulletStartPos.GetComponent<DWM_Shoot>().enabled = false;
    centerEyeAnchor.SetActive (false);
    audioSourceObject.SetActive(false);
    }

    SetPlayerUI();

    }

    private void Update()
    {
    if (PhotonNetwork.IsMasterClient)
    hostDSP = AudioSettings.dspTime;

    }

    void SetPlayerUI()
    {
    if (playerNameText != null)
    {
    playerNameText.text = photonView.Owner.NickName;
    Debug.Log("Player has been named. - DWM");
    }
    }

    // Per John French MusicController script:
    double GetClipLength()
    {
    sampleRate = audioSource.clip.frequency;
    double length = (double)audioSource.clip.samples / sampleRate;
    return length;
    }

    void StartMusic()
    {
    firstStartTime = AudioSettings.dspTime + 1;
    audioSource.PlayScheduled(firstStartTime);
    }

    public override void OnPlayerEnteredRoom(Player newPlayer)
    {
    gameObject.GetComponent<PhotonView>().RPC("StartMusicWithDelay", RpcTarget.AllBuffered, firstStartTime, hostDSP);
    Debug.Log(newPlayer.NickName + " joined " + PhotonNetwork.CurrentRoom.Name + ". " + "Player count = " + PhotonNetwork.CurrentRoom.PlayerCount + ". - DWM");
    }

    [PunRPC] // Adapted from John's Unity forum post:
    void StartMusicWithDelay(double _firstStartTime, double _hostDSP)
    {
    double hostMachineTime = _hostDSP + 1;
    double localStartTime = AudioSettings.dspTime + 1;
    double timeSinceStart = (_hostDSP - _firstStartTime) % clipLength;
    int samplePosition = (int)(sampleRate * timeSinceStart);
    audioSource.timeSamples = samplePosition;
    audioSource.PlayScheduled(localStartTime);
    Debug.Log("StartMusicWithDelay was called. _hostDSP = "+ _hostDSP+". _firstStartTime = "+_firstStartTime);
    }

    /* With the above setup StartMusicWithDelay is called when the second player enters the room and the _hostDSP and _firstStartTime values are shown
    in the Unity Console. In the Unity Editor the firstStartTime value is shown for the first player and is zero for the second player.
    hostDSP is shown with the same value for both players in the Editor. The music for the first player pauses for about a second when the second player enters.
    The music doesn't play for the second player.*/
    }