Code/TrackingAudioAccessor.cs
using Sandbox.Audio;
using System;

namespace Duccsoft;

/// <summary>
/// Stores and applies the audio settings of a VideoPlayer, with the option
/// to have the audio position track a GameObject.
/// </summary>
public class TrackingAudioAccessor : IAudioAccessor, IMediaUpdateListener
{
	/// <summary>
	/// Creates a new TrackingAudioAccessort that does not yet target a VideoPlayer.
	/// </summary>
	public TrackingAudioAccessor() 
	{
		IMediaUpdateListener.Register( this );
	}
	
	/// <summary>
	/// Creates a new TrackingAudioAccessor targeting the specified VideoPlayer.
	/// </summary>
	public TrackingAudioAccessor( VideoPlayer videoPlayer )
	{
		VideoPlayer = videoPlayer;
		IMediaUpdateListener.Register( this );
	}

	/// <summary>
	/// The instance of VideoPlayer that shall have its audio settings changed by this object.
	/// </summary>
	public VideoPlayer VideoPlayer 
	{
		get => _videoPlayer;
		set
		{
			_videoPlayer = value;
			MediaUpdate();
		}
	}
	private VideoPlayer _videoPlayer;

	/// <summary>
	/// The GameObject from which to play the sound. The sound will automatically
	/// follow the GameObject.
	/// </summary>
	public GameObject Target 
	{
		get => _target;
		set
		{
			_target = value;
			_listenLocal = !_target.IsValid();
			if ( !_listenLocal )
			{
				_position = _target.WorldPosition;
			}
			MediaUpdate();
		}
	}
	private GameObject _target;

	/// <summary>
	/// The position from which to play the sound. Setting this will also
	/// set <see cref="Target"/> to null.
	/// </summary>
	public Vector3 Position 
	{
		get => _position;
		set
		{
			_position = value;
			_target = null;
			MediaUpdate();
		}
	}
	private Vector3 _position;

	/// <inheritdoc cref="IAudioAccessor.Volume"/>
	public float Volume 
	{ 
		get => _volume;
		set
		{
			_volume = value;
			MediaUpdate();
		}
	}
	private float _volume = 1f;

	/// <inheritdoc cref="IAudioAccessor.ListenLocal"/>
	public bool ListenLocal 
	{
		get => _listenLocal;
		set
		{
			_listenLocal = value;
			if ( _listenLocal )
			{
				_target = null;
			}
			MediaUpdate();
		}
	}
	private bool _listenLocal;

	/// <inheritdoc cref="IAudioAccessor.TargetMixer"/>
	public Mixer TargetMixer
	{
		get => _targetMixer;
		set
		{
			_targetMixer = value;
			MediaUpdate();
		}
	}
	private Mixer _targetMixer;

	/// <inheritdoc cref="IAudioAccessor.Muted"/>
	public bool Muted
	{
		get => _muted;
		set 
		{
			_muted = value;
			MediaUpdate();
		}
	}
	private bool _muted;

	/// <summary>
	/// Updates properties of the underlying audio player to match the values specified in this object.
	/// This will automatically be called on every frame.
	/// </summary>
	public void MediaUpdate()
	{
		if ( VideoPlayer is null )
			return;

		if ( Target.IsValid() )
		{
			_position = Target.WorldPosition;
		}

		VideoPlayer.Muted = Muted;
		VideoPlayer.Audio.Position = Position;
		VideoPlayer.Audio.Volume = Volume;
		VideoPlayer.Audio.ListenLocal = ListenLocal;
		VideoPlayer.Audio.TargetMixer = TargetMixer;
	}

	/// <summary>
	/// Unregisters this <see cref="IMediaUpdateListener"/> from <see cref="MediaUpdateSystem"/>.
	/// </summary>
	public void Dispose()
	{
		IMediaUpdateListener.Unregister( this );
		GC.SuppressFinalize( this );
	}
}