I have been trying to play an effect sound whenever the function is called, however I keep getting the notification "Cannot play a disabled audio source". I also noticed that the sound plays on awake even though I have it turned off. Here's what my EffectController class looks like
//--------------------------------------------------------------------------------------------------------------------
// <copyright file="EffectController.cs" company="ShinjiGames">
// Copyright (c) ShinjiGames LLC 2016. All rights reserved.
// </copyright>
//--------------------------------------------------------------------------------------------------------------------
namespace Assets.Scripts.Effects
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GameSettings;
using UnityEngine;
/// <summary>
/// Responsible for audio cues and other add-on effects
/// </summary>
public class EffectController : MonoBehaviour
{
/// <summary>
/// The sound that plays whenever the player hits a pickup
/// </summary>
public AudioSource PickupChime;
/// <summary>
/// The current instance of the effect controller
/// </summary>
private static EffectController instance;
/// <summary>
/// Get the instance of the effect controller
/// </summary>
/// <returns>The instance of the effect controller</returns>
public static EffectController GetInstance()
{
return EffectController.instance;
}
/// <summary>
/// When the player hits a pickup on the given position, plays the chime and add an effect
/// </summary>
/// <param name="position">The position of the collision</param>
public void PickupEffect(Vector3 position)
{
this.PickupChime.enabled = true;
this.PickupChime.PlayOneShot(this.PickupChime.clip);
}
/// <summary>
/// When the players hits a bomb, display the bomb explosion
/// </summary>
/// <param name="position">The origin of the explosion</param>
public void BombEffect(Vector3 position)
{
var explosion = GameObject.Instantiate(PrefabManager.GetIntance().BombExplosionPrefab);
explosion.GetComponent<BombExplosion>().SetOrigin(position);
}
/// <summary>
/// Initialization for the class
/// </summary>
private void Start()
{
this.PickupChime.enabled = true;
this.GetComponent<AudioSource>().enabled = true;
if (EffectController.instance != null)
{
GameObject.Destroy(EffectController.instance.gameObject);
}
EffectController.instance = this;
GameObject.DontDestroyOnLoad(this.gameObject);
}
}
}
Can anyone give me a hand? I am slowly going crazy
Edit 1: I updated the class so that instead of taking in an AudioSource, it have it's own component for AudioSource and takes in the clip to the chime instead, which solves the problem.
You may have had the Audio Source unchecked in the inspector.
Related
I have a class that implements the IDisposable interface.
using System;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// <para>
/// The Engine Timer allows for starting a timer that will execute a callback at a given interval.
/// </para>
/// <para>
/// The timer may fire:
/// - infinitely at the given interval
/// - fire once
/// - fire _n_ number of times.
/// </para>
/// <para>
/// The Engine Timer will stop its self when it is disposed of.
/// </para>
/// <para>
/// The Timer requires you to provide it an instance that will have an operation performed against it.
/// The callback will be given the generic instance at each interval fired.
/// </para>
/// <para>
/// In the following example, the timer is given an instance of an IPlayer.
/// It starts the timer off with a 30 second delay before firing the callback for the first time.
/// It tells the timer to fire every 60 seconds with 0 as the number of times to fire. When 0 is provided, it will run infinitely.
/// Lastly, it is given a callback, which will save the player every 60 seconds.
/// #code
/// var timer = new EngineTimer<IPlayer>(new DefaultPlayer());
/// timer.StartAsync(30000, 6000, 0, (player, timer) => player.Save());
/// #endcode
/// </para>
/// </summary>
/// <typeparam name="T">The type that will be provided when the timer callback is invoked.</typeparam>
public sealed class EngineTimer<T> : CancellationTokenSource, IDisposable
{
/// <summary>
/// The timer task
/// </summary>
private Task timerTask;
/// <summary>
/// How many times we have fired the timer thus far.
/// </summary>
private long fireCount = 0;
/// <summary>
/// Initializes a new instance of the <see cref="EngineTimer{T}"/> class.
/// </summary>
/// <param name="callback">The callback.</param>
/// <param name="state">The state.</param>
public EngineTimer(T state)
{
if (state == null)
{
throw new ArgumentNullException(nameof(state), "EngineTimer constructor requires a non-null argument.");
}
this.StateData = state;
}
/// <summary>
/// Gets the object that was provided to the timer when it was instanced.
/// This object will be provided to the callback at each interval when fired.
/// </summary>
public T StateData { get; private set; }
/// <summary>
/// Gets a value indicating whether the engine timer is currently running.
/// </summary>
public bool IsRunning { get; private set; }
/// <summary>
/// <para>
/// Starts the timer, firing a synchronous callback at each interval specified until `numberOfFires` has been reached.
/// If `numberOfFires` is 0, then the callback will be called indefinitely until the timer is manually stopped.
/// </para>
/// <para>
/// The following example shows how to start a timer, providing it a callback.
/// </para>
/// #code
/// var timer = new EngineTimer<IPlayer>(new DefaultPlayer());
/// double startDelay = TimeSpan.FromSeconds(30).TotalMilliseconds;
/// double interval = TimeSpan.FromMinutes(10).TotalMilliseconds;
/// int numberOfFires = 0;
///
/// timer.Start(
/// startDelay,
/// interval,
/// numberOfFires,
/// (player, timer) => player.Save());
/// #endcode
/// </summary>
/// <param name="startDelay">
/// <para>
/// The `startDelay` is used to specify how much time must pass before the timer can invoke the callback for the first time.
/// If 0 is provided, then the callback will be invoked immediately upon starting the timer.
/// </para>
/// <para>
/// The `startDelay` is measured in milliseconds.
/// </para>
/// </param>
/// <param name="interval">The interval in milliseconds.</param>
/// <param name="numberOfFires">Specifies the number of times to invoke the timer callback when the interval is reached. Set to 0 for infinite.</param>
public void Start(double startDelay, double interval, int numberOfFires, Action<T, EngineTimer<T>> callback)
{
this.IsRunning = true;
this.timerTask = Task
.Delay(TimeSpan.FromMilliseconds(startDelay), this.Token)
.ContinueWith(
(task, state) => RunTimer(task, (Tuple<Action<T, EngineTimer<T>>, T>)state, interval, numberOfFires),
Tuple.Create(callback, this.StateData),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
}
/// <summary>
/// Starts the specified start delay.
/// </summary>
/// <param name="startDelay">The start delay in milliseconds.</param>
/// <param name="interval">The interval in milliseconds.</param>
/// <param name="numberOfFires">Specifies the number of times to invoke the timer callback when the interval is reached. Set to 0 for infinite.</param>
public void StartAsync(double startDelay, double interval, int numberOfFires, Func<T, EngineTimer<T>, Task> callback)
{
this.IsRunning = true;
this.timerTask = Task
.Delay(TimeSpan.FromMilliseconds(startDelay), this.Token)
.ContinueWith(
async (task, state) => await RunTimerAsync(task, (Tuple<Func<T, EngineTimer<T>, Task>, T>)state, interval, numberOfFires),
Tuple.Create(callback, this.StateData),
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
}
/// <summary>
/// Stops the timer for this instance.
/// Stopping the timer will not dispose of the EngineTimer, allowing you to restart the timer if you need to.
/// </summary>
public void Stop()
{
if (!this.IsCancellationRequested)
{
this.Cancel();
}
this.IsRunning = false;
}
/// <summary>
/// Stops the timer and releases the unmanaged resources used by the <see cref="T:System.Threading.CancellationTokenSource" /> class and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.IsRunning = false;
this.Cancel();
}
base.Dispose(disposing);
}
private async Task RunTimer(Task task, Tuple<Action<T, EngineTimer<T>>, T> state, double interval, int numberOfFires)
{
while (!this.IsCancellationRequested)
{
// Only increment if we are supposed to.
if (numberOfFires > 0)
{
this.fireCount++;
}
state.Item1(state.Item2, this);
await PerformTimerCancellationCheck(interval, numberOfFires);
}
}
private async Task RunTimerAsync(Task task, Tuple<Func<T, EngineTimer<T>, Task>, T> state, double interval, int numberOfFires)
{
while (!this.IsCancellationRequested)
{
// Only increment if we are supposed to.
if (numberOfFires > 0)
{
this.fireCount++;
}
await state.Item1(state.Item2, this);
await PerformTimerCancellationCheck(interval, numberOfFires);
}
}
private async Task PerformTimerCancellationCheck(double interval, int numberOfFires)
{
// If we have reached our fire count, stop. If set to 0 then we fire until manually stopped.
if (numberOfFires > 0 && this.fireCount >= numberOfFires)
{
this.Stop();
}
await Task.Delay(TimeSpan.FromMilliseconds(interval), this.Token).ConfigureAwait(false);
}
}
I then created a series of unit tests for the class.
[TestClass]
public class EngineTimerTests
{
[TestMethod]
[TestCategory("MudDesigner")]
[TestCategory("Engine")]
[TestCategory("Engine Core")]
[Owner("Johnathon Sullinger")]
[ExpectedException(typeof(ArgumentNullException))]
public void Exception_thrown_with_null_ctor_argument()
{
// Act
new EngineTimer<ComponentFixture>(null);
}
[TestMethod]
[TestCategory("MudDesigner")]
[TestCategory("Engine")]
[TestCategory("Engine Core")]
[Owner("Johnathon Sullinger")]
public void Ctor_sets_state_property()
{
// Arrange
var fixture = new ComponentFixture();
// Act
var engineTimer = new EngineTimer<ComponentFixture>(fixture);
// Assert
Assert.IsNotNull(engineTimer.StateData, "State was not assigned from the constructor.");
Assert.AreEqual(fixture, engineTimer.StateData, "An incorrect State object was assigned to the timer.");
}
[TestMethod]
[TestCategory("MudDesigner")]
[TestCategory("Engine")]
[TestCategory("Engine Core")]
[Owner("Johnathon Sullinger")]
public void Start_sets_is_running()
{
// Arrange
var fixture = new ComponentFixture();
var engineTimer = new EngineTimer<ComponentFixture>(fixture);
// Act
engineTimer.Start(0, 1, 0, (component, timer) => { });
// Assert
Assert.IsTrue(engineTimer.IsRunning, "Engine Timer was not started.");
}
[TestMethod]
[TestCategory("MudDesigner")]
[TestCategory("Engine")]
[TestCategory("Engine Core")]
[Owner("Johnathon Sullinger")]
public void Callback_invoked_when_running()
{
// Arrange
var fixture = new ComponentFixture();
var engineTimer = new EngineTimer<ComponentFixture>(fixture);
bool callbackInvoked = false;
// Act
engineTimer.Start(0, 1, 0, (component, timer) => { callbackInvoked = true; });
Task.Delay(20);
// Assert
Assert.IsTrue(callbackInvoked, "Engine Timer did not invoke the callback as expected.");
}
}
When I run the unit test coverage analysis in Visual Studio 2015, it tells me that the class is 100% covered by unit tests. However, I've only tested the constructor and the Start() method. None of the unit tests touch the Stop(), StartAsync() or Dispose() methods.
Why would Visual Studio tell me I am at 100% code coverage?
Update
I turned on coverage highlights and discovered that the Stop() method is not covered (if I read this right).
It is interesting that the analysis tells me it's 100% covered, even though the coverage highlights shows that it's not included in any unit test paths.
The simplest way to explain the way code coverage works is as the following:
Take all dlls from the target directory and build a directed graph G based on the IL code.
Use G and creates all possible directed-paths.
Execute the tests and mark the relevant paths.
Calculate the percentage.
Methods with 100% code coverage means you walk through all the method's paths, during your UT's execution.(Basically the tool doesn't know which class in under test in the UT)
Based on the above description the CC's behavior you've faced could happened from at least one of the following options:
Bug in the code coverage tool
I've faced something similar when my CC tool worked on deffrent version of my dlls.(Rebuild Solution solved the problem in this case)
At least one of your tests calls those methods directly.
At least one of your tests calls those methods indirectly: Through inheritance, composition
and etc...
To understand the reason I offer you to follow:
If you want to see the coverage from specific test class it's very easy:
In "Test Explorer" right click -> Group By -> Class
Select the class you want to monitor.
Right click -> Analyze Code Coverage For Selected Tests.
Well,
internal Dispose can be called during finalization, so it may happen.
Stop() can be called by PerformTimerCancellationCheck->RunTimer->Start-> Start_sets_is_running
StartAsync apparently not calling.
Detecting code coverage is non-trivial, considering various optimizations and code generation. I would probably tolerate 10%-20% error from the analyzer and prefer to focus on checking whether the class really works. Code coverage actually shows that the the block is "visited" not that everything in it works as expected. But of course if the block is not visited at all, that's the problem.
Problem
My application use accelerometer data via Microsoft.Devices.Sensors.Accelerometer class
Now I wanna write Unit or CodedUI tests, for this reason I need to have a special test implementation of Accelerometer to controlling returned data
In theory I have 3 options:
Create subclass of Accelerometer - impossible because Accelerometer is sealed class
By using reflection replace implementation of Accelerometer at run-time - isn't best option IMHO
Create subclass of SensorBase<AccelerometerReading> - good option
I have decided to go by #1 and implement my TestAccelerometer as child of SensorBase<AccelerometerReading>
And here is I have problem:
`Error CS1729 'SensorBase<AccelerometerReading>' does not contain a constructor that takes 0 arguments`
Error pretty clear I have looked in decompiled code (JetBrains dotPeak tool) and don't found any constructors at all.
Questions
How can I found proper constructor to use in my TestAccelerometer implementation?
Decompiled code
Microsoft.Devices.Sensors.Accelerometer
// Type: Microsoft.Devices.Sensors.Accelerometer
// Assembly: Microsoft.Devices.Sensors, Version=8.0.0.0, Culture=neutral, PublicKeyToken=24eec0d8c86cda1e
// MVID: 81ED89AA-6B11-4B39-BAFA-38D59CBB1E3B
// Assembly location: C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\WindowsPhone\v8.0\Microsoft.Devices.Sensors.dll
using System;
namespace Microsoft.Devices.Sensors
{
/// <summary>
/// Provides Windows Phone applications access to the device’s accelerometer sensor.
/// </summary>
public sealed class Accelerometer : SensorBase<AccelerometerReading>
{
/// <summary>
/// Gets or sets whether the device on which the application is running supports the accelerometer sensor.
/// </summary>
///
/// <returns>
/// true if the device on which the application is running supports the accelerometer sensor; otherwise, false.
/// </returns>
public static bool IsSupported { get; internal set; }
/// <summary>
/// Gets the current state of the accelerometer. The value is a member of the <see cref="T:Microsoft.Devices.Sensors.SensorState"/> enumeration.
/// </summary>
///
/// <returns>
/// Type: <see cref="T:Microsoft.Devices.Sensors.SensorState"/>.
/// </returns>
public SensorState State { get; private set; }
/// <summary>
/// Occurs when new data arrives from the accelerometer. This method is deprecated in the current release. Applications should use the <see cref="E:Microsoft.Devices.Sensors.SensorBase`1.CurrentValueChanged"/> event of the <see cref="T:Microsoft.Devices.Sensors.SensorBase`1"/> class instead.
/// </summary>
[Obsolete("use CurrentValueChanged")]
public event EventHandler<AccelerometerReadingEventArgs> ReadingChanged;
static Accelerometer();
/// <summary>
/// Releases the managed and unmanaged resources used by the <see cref="T:Microsoft.Devices.Sensors.Accelerometer"/>.
/// </summary>
public new void Dispose();
/// <summary>
/// Starts data acquisition from the accelerometer.
/// </summary>
public new void Start();
/// <summary>
/// Stops data acquisition from the accelerometer.
/// </summary>
public new void Stop();
}
}
Microsoft.Devices.Sensors.SensorBase
// Type: Microsoft.Devices.Sensors.SensorBase`1
// Assembly: Microsoft.Devices.Sensors, Version=8.0.0.0, Culture=neutral, PublicKeyToken=24eec0d8c86cda1e
// MVID: 81ED89AA-6B11-4B39-BAFA-38D59CBB1E3B
// Assembly location: C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\WindowsPhone\v8.0\Microsoft.Devices.Sensors.dll
using System;
using System.Security;
namespace Microsoft.Devices.Sensors
{
/// <summary>
/// The base class for all Windows Phone sensor classes.
/// </summary>
/// <typeparam name="TSensorReading">The type of reading returned by the sensor.</typeparam>
public abstract class SensorBase<TSensorReading> : IDisposable where TSensorReading : ISensorReading
{
/// <summary>
/// Gets an object that implements <see cref="T:Microsoft.Devices.Sensors.ISensorReading"/> that contains the current value of the sensor. This object will be one of the following types, depending on which sensor is being referenced: <see cref="T:Microsoft.Devices.Sensors.AccelerometerReading"/>, <see cref="T:Microsoft.Devices.Sensors.CompassReading"/>, <see cref="T:Microsoft.Devices.Sensors.GyroscopeReading"/>, <see cref="T:Microsoft.Devices.Sensors.MotionReading"/>.
/// </summary>
///
/// <returns>
/// An object that implements <see cref="T:Microsoft.Devices.Sensors.ISensorReading"/> that contains the current value of the sensor.
/// </returns>
public TSensorReading CurrentValue { get; }
/// <summary>
/// Gets or sets the preferred time between <see cref="E:Microsoft.Devices.Sensors.SensorBase`1.CurrentValueChanged"/> events.
/// </summary>
///
/// <returns>
/// Type: <see cref="T:System.TimeSpan"/>. The preferred time between CurrentValueChanged events.
/// </returns>
public TimeSpan TimeBetweenUpdates { get; set; }
/// <summary>
/// Gets the validity of the sensor’s data.
/// </summary>
///
/// <returns>
/// Type: <see cref="T:System.Boolean"/>. true if the sensor’s data is valid; otherwise, false.
/// </returns>
public bool IsDataValid { get; }
/// <summary>
/// Occurs when new data arrives from the sensor.
/// </summary>
public event EventHandler<SensorReadingEventArgs<TSensorReading>> CurrentValueChanged;
static SensorBase();
internal SensorBase();
/// <summary>
/// Allows an object to try to free resources and perform other cleanup operations before the Object is reclaimed by garbage collection.
/// </summary>
[SecuritySafeCritical]
~SensorBase();
/// <summary>
/// Releases the managed and unmanaged resources used by the sensor.
/// </summary>
[SecuritySafeCritical]
public void Dispose();
/// <summary>
/// Starts acquisition of data from the sensor.
/// </summary>
public virtual void Start();
/// <summary>
/// Stops acquisition of data from the sensor.
/// </summary>
public virtual void Stop();
}
}
You did find a constructor:
internal SensorBase();
This constructor prevents the SensorBase from having a default public constructor and this class cannot be subclassed from outside its assembly.
Testing it will be complicated. You could redesign the app and use a wrapper/adapter for the Accelerometer just for the puropse of testing or you could check if there's a possibility of mocking a sealed class on Windows Phone.
I have an operation contract in my service contract called
Schedule[] GetScheduleObjects();
I have a datameber in that operation contract called "Tasks" that returns a list of sub objects.
[DataMember()]
public Task[] Tasks
The issues is when the operation contract is called the method executes but the "get" of "Tasks" occurs twice. The first time it contains valid runtime instances, the second time it is null which causes a serialization exception. This happens despite only one call to the service. The binding is a tcp connection using a duplex proxy. Ideas?????
The Datacontract
[DataContract()]
public class Schedule
{
public Schedule(string name)
{
this.Name = name;
}
[DataMember()]
public string Name { get; private set; }
[DataMember()]
public bool Running { get; set; }
/// <summary>
/// Schedule Task is a DataMember object, do not modify
/// </summary>
[DataMember()]
public Task[] Tasks
{
get { return _Tasks.ToArray(); }
}
private List<Task> _Tasks = new List<Task>();
///<summary>
/// Use this property to add objects
///</summary>
public List<Task> ScheduleTasks
{
get { return _Tasks; }
}
}
The service contract
[ServiceContract(CallbackContract = typeof(ISummitDashboardCallbackContract))]
public interface ISchedulerContract : ISummitDashboardContract
{
/// <summary>
/// Sets the named schedule into "run" mode
/// </summary>
/// <param name="scheduleName"></param>
/// <returns></returns>
[OperationContract()]
void StartSchedule(string scheduleName);
/// <summary>
/// Pauses the currently running schedule
/// </summary>
/// <param name="scheduleName"></param>
[OperationContract()]
void PauseSchedule(string scheduleName);
/// <summary>
/// Removes the named schedule from "run" mode
/// </summary>
/// <param name="scheduleName"></param>
/// <returns></returns>
[OperationContract()]
void StopSchedule(string scheduleName);
/// <summary>
/// Flips the "active" state of the task with the named id
/// </summary>
/// <param name="scheduleName"></param>
/// <param name="Id"></param>
[OperationContract()]
void ToggleTaskState(string scheduleName, string Id);
/// <summary>
/// Flips the "active" state of the action with the named id
/// </summary>
/// <param name="scheduleName"></param>
/// <param name="Id"></param>
/// <returns></returns>
[OperationContract()]
void ToggleActionState(string scheduleName, string Id);
/// <summary>
/// Returns the information to build the tree list in the dashboard
/// </summary>
/// <returns></returns>
[OperationContract()]
Schedule[] GetScheduleObjects();
/// <summary>
/// Returns the events of the scheduler
/// </summary>
/// <returns></returns>
[OperationContract()]
SchedulerEvent[] GetSchedulerEvents(int startIndex, int count, int eventLogEntryType);
}
You'll need to add a setter to property Tasks, and you'll also need to increase the visibility of Name to at least protected - WCF will need to use these in order to deserialize objects of this class.
As a secondary issue, if a client generates a proxy (e.g. with Add Service Reference or via SvcUtil.exe), the 'code behind' Tasks (i.e. return _Tasks.ToArray(); which is coupled to ScheduledTasks) will be lost, and the client will instead just get a simple automatic backing property to property Tasks (with the collection class selected during the proxy generation). This second issue won't however happen however if you share type.
Using C# i'm trying to retrieve the name of the song that is currently playing and display it on a listBox, so every song that plays, it's shown in the listbox.
Using System;
Using WMPLib;
public IWMPMedia currentMedia { get; set; }
private void button1_Click(object sender, EventArgs e)
{
Player = new WMPLib.WindowsMediaPlayer();
string song = Player.currentMedia.name.ToString();
listBox1.Items.Add(song);
}
But it throws me the exception.
"Object reference not set to an instance of an object" here:
string song = Player.currentMedia.name.ToString();
Does anyone knows how to solve this?
You will have to use COM/OLE to do this. I did a program that did exactly that a little while ago, unfortunately tho I cannot find my client code, but I still have the code that implements IOleClientSite/IOleServiceProvider for WMPLib.
I found that code at the following URL:
http://sirayuki.sakura.ne.jp/WmpSample/WmpRemote.zip
Its some code that was writen by Jonathan Dibble, a Microsoft employee if I remember correctly. There is a CHM in the Zip with some explanation on each of the classes.
Heres the code that I still have, in case the link goes down, but like I said I cannot find my code that uses it. It worked almost correctly but I remember that one of the bug left was that it would leave behind wmplayer.exe processes after the media player and my application were closed.
UPDATE!
found some client code and a slighlty modified version of RemotedWindowsMediaPlayer.cs
I just found some coded that I tested this with, and it works. Its from a Winform project, and you need to reference WMPLib for this to work.
In my form I added a button and this code:
/// <summary>
/// Default Constructor
/// </summary>
RemotedWindowsMediaPlayer rm;
public FrmMain()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//Call me old fashioned - I like to do this stuff manually. You can do a drag
//drop if you like, it won't change the results.
rm = new RemotedWindowsMediaPlayer();
rm.Dock = System.Windows.Forms.DockStyle.Top;
panel1.Controls.Add(rm);
return;
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(((WMPLib.IWMPPlayer4)rm.GetOcx()).currentMedia.sourceURL);
}
RemotedWindowsMediaPLayer.cs:
namespace RemoteWMP
{
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using WMPLib;
/// <summary>
/// This is the actual Windows Media Control.
/// </summary>
[System.Windows.Forms.AxHost.ClsidAttribute("{6bf52a52-394a-11d3-b153-00c04f79faa6}")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class RemotedWindowsMediaPlayer : System.Windows.Forms.AxHost,
IOleServiceProvider,
IOleClientSite
{
/// <summary>
/// Used to attach the appropriate interface to Windows Media Player.
/// In here, we call SetClientSite on the WMP Control, passing it
/// the dotNet container (this instance.)
/// </summary>
protected override void AttachInterfaces()
{
try
{
//Get the IOleObject for Windows Media Player.
IOleObject oleObject = this.GetOcx() as IOleObject;
if (oleObject != null)
{
//Set the Client Site for the WMP control.
oleObject.SetClientSite(this as IOleClientSite);
// Try and get the OCX as a WMP player
if (this.GetOcx() as IWMPPlayer4 == null)
{
throw new Exception(string.Format("OCX is not an IWMPPlayer4! GetType returns '{0}'",
this.GetOcx().GetType()));
}
}
else
{
throw new Exception("Failed to get WMP OCX as an IOleObject?!");
}
return;
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
#region IOleServiceProvider Memebers - Working
/// <summary>
/// During SetClientSite, WMP calls this function to get the pointer to <see cref="RemoteHostInfo"/>.
/// </summary>
/// <param name="guidService">See MSDN for more information - we do not use this parameter.</param>
/// <param name="riid">The Guid of the desired service to be returned. For this application it will always match
/// the Guid of <see cref="IWMPRemoteMediaServices"/>.</param>
/// <returns></returns>
IntPtr IOleServiceProvider.QueryService(ref Guid guidService, ref Guid riid)
{
//If we get to here, it means Media Player is requesting our IWMPRemoteMediaServices interface
if (riid == new Guid("cbb92747-741f-44fe-ab5b-f1a48f3b2a59"))
{
IWMPRemoteMediaServices iwmp = new RemoteHostInfo();
return Marshal.GetComInterfaceForObject(iwmp, typeof(IWMPRemoteMediaServices));
}
throw new System.Runtime.InteropServices.COMException("No Interface", (int) HResults.E_NOINTERFACE);
}
#endregion
#region IOleClientSite Members
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException">E_NOTIMPL</exception>
void IOleClientSite.SaveObject()
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
object IOleClientSite.GetMoniker(uint dwAssign, uint dwWhichMoniker)
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
object IOleClientSite.GetContainer()
{
return (int)HResults.E_NOINTERFACE;
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
void IOleClientSite.ShowObject()
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
void IOleClientSite.OnShowWindow(bool fShow)
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
/// <summary>
/// Not in use. See MSDN for details.
/// </summary>
/// <exception cref="System.Runtime.InteropServices.COMException"></exception>
void IOleClientSite.RequestNewObjectLayout()
{
throw new System.Runtime.InteropServices.COMException("Not Implemented", (int) HResults.E_NOTIMPL);
}
#endregion
/// <summary>
/// Default Constructor.
/// </summary>
public RemotedWindowsMediaPlayer() :
base("6bf52a52-394a-11d3-b153-00c04f79faa6")
{
}
}
}
RemoteHostInfo.cs
using System;
namespace RemoteWMP
{
using System.Runtime.InteropServices;
/// <summary>
/// This class contains the information to return to Media Player about our remote service.
/// </summary>
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class RemoteHostInfo :
IWMPRemoteMediaServices
{
#region IWMPRemoteMediaServices Members
/// <summary>
/// Returns "Remote" to tell media player that we want to remote the WMP application.
/// </summary>
/// <returns></returns>
public string GetServiceType()
{
return "Remote";
}
/// <summary>
/// The Application Name to show in Windows Media Player switch to menu
/// </summary>
/// <returns></returns>
public string GetApplicationName()
{
return System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}
/// <summary>
/// Not in use, see MSDN for more info.
/// </summary>
/// <param name="name"></param>
/// <param name="dispatch"></param>
/// <returns></returns>
public HResults GetScriptableObject(out string name, out object dispatch)
{
name = null;
dispatch = null;
//return (int) HResults.S_OK;//NotImplemented
return HResults.E_NOTIMPL;
}
/// <summary>
/// For skins, not in use, see MSDN for more info.
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
public HResults GetCustomUIMode(out string file)
{
file = null;
return HResults.E_NOTIMPL;//NotImplemented
}
#endregion
}
}
COM Interfaces.cs
using System;
namespace RemoteWMP
{
using System.Runtime.InteropServices;
#region Useful COM Enums
/// <summary>
/// Represents a collection of frequently used HRESULT values.
/// You may add more HRESULT VALUES, I've only included the ones used
/// in this project.
/// </summary>
public enum HResults
{
/// <summary>
/// HRESULT S_OK
/// </summary>
S_OK = unchecked((int)0x00000000),
/// <summary>
/// HRESULT S_FALSE
/// </summary>
S_FALSE = unchecked((int)0x00000001),
/// <summary>
/// HRESULT E_NOINTERFACE
/// </summary>
E_NOINTERFACE = unchecked((int)0x80004002),
/// <summary>
/// HRESULT E_NOTIMPL
/// </summary>
E_NOTIMPL = unchecked((int)0x80004001),
/// <summary>
/// USED CLICKED CANCEL AT SAVE PROMPT
/// </summary>
OLE_E_PROMPTSAVECANCELLED = unchecked((int)0x8004000C),
}
/// <summary>
/// Enumeration for <see cref="IOleObject.GetMiscStatus"/>
/// </summary>
public enum DVASPECT
{
/// <summary>
/// See MSDN for more information.
/// </summary>
Content = 1,
/// <summary>
/// See MSDN for more information.
/// </summary>
Thumbnail = 2,
/// <summary>
/// See MSDN for more information.
/// </summary>
Icon = 3,
/// <summary>
/// See MSDN for more information.
/// </summary>
DocPrint = 4
}
/// <summary>
/// Emumeration for <see cref="IOleObject.Close"/>
/// </summary>
public enum TAGOLECLOSE :uint{
OLECLOSE_SAVEIFDIRTY = unchecked((int)0),
OLECLOSE_NOSAVE = unchecked((int)1),
OLECLOSE_PROMPTSAVE = unchecked((int)2)
}
#endregion
#region IWMPRemoteMediaServices
/// <summary>
/// Interface used by Media Player to determine WMP Remoting status.
/// </summary>
[ComImport,
ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("CBB92747-741F-44fe-AB5B-F1A48F3B2A59")]
public interface IWMPRemoteMediaServices
{
/// <summary>
/// Service type.
/// </summary>
/// <returns><code>Remote</code> if the control is to be remoted (attached to WMP.)
/// <code>Local</code>if this is an independent WMP instance not connected to WMP application. If you want local, you shouldn't bother
/// using this control!
/// </returns>
[return: MarshalAs(UnmanagedType.BStr)]
string GetServiceType();
/// <summary>
/// Value to display in Windows Media Player Switch To Application menu option (under View.)
/// </summary>
/// <returns></returns>
[return: MarshalAs(UnmanagedType.BStr)]
string GetApplicationName();
/// <summary>
/// Not in use, see MSDN for details.
/// </summary>
/// <param name="name"></param>
/// <param name="dispatch"></param>
/// <returns></returns>
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
HResults GetScriptableObject([MarshalAs(UnmanagedType.BStr)] out string name,
[MarshalAs(UnmanagedType.IDispatch)] out object dispatch);
/// <summary>
/// Not in use, see MSDN for details.
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
HResults GetCustomUIMode([MarshalAs(UnmanagedType.BStr)] out string file);
}
#endregion
#region IOleServiceProvider
/// <summary>
/// Interface used by Windows Media Player to return an instance of IWMPRemoteMediaServices.
/// </summary>
[ComImport,
GuidAttribute("6d5140c1-7436-11ce-8034-00aa006009fa"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
ComVisible(true)]
public interface IOleServiceProvider
{
/// <summary>
/// Similar to QueryInterface, riid will contain the Guid of an object to return.
/// In our project we will look for <see cref="IWMPRemoteMediaServices"/> Guid and return the object
/// that implements that interface.
/// </summary>
/// <param name="guidService"></param>
/// <param name="riid">The Guid of the desired Service to provide.</param>
/// <returns>A pointer to the interface requested by the Guid.</returns>
IntPtr QueryService(ref Guid guidService, ref Guid riid);
}
/// <summary>
/// This is an example of an INCORRECT entry - do not use, unless you want your app to break.
/// </summary>
[ComImport,
GuidAttribute("6d5140c1-7436-11ce-8034-00aa006009fa"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
ComVisible(true)]
public interface BadIOleServiceProvider
{
/// <summary>
/// This is incorrect because it causes our return interface to be boxed
/// as an object and a COM callee may not get the correct pointer.
/// </summary>
/// <param name="guidService"></param>
/// <param name="riid"></param>
/// <returns></returns>
/// <example>
/// For an example of a correct definition, look at <see cref="IOleServiceProvider"/>.
/// </example>
[return: MarshalAs(UnmanagedType.Interface)]
object QueryService(ref Guid guidService, ref Guid riid);
}
#endregion
#region IOleClientSite
/// <summary>
/// Need to implement this interface so we can pass it to <see cref="IOleObject.SetClientSite"/>.
/// All functions return E_NOTIMPL. We don't need to actually implement anything to get
/// the remoting to work.
/// </summary>
[ComImport,
ComVisible(true),
Guid("00000118-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown) ]
public interface IOleClientSite
{
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
void SaveObject();
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
[return: MarshalAs(UnmanagedType.Interface)]
object GetMoniker(uint dwAssign, uint dwWhichMoniker);
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
[return: MarshalAs(UnmanagedType.Interface)]
object GetContainer();
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
void ShowObject();
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
void OnShowWindow(bool fShow);
/// <summary>
/// See MSDN for more information. Throws <see cref="COMException"/> with id of E_NOTIMPL.
/// </summary>
/// <exception cref="COMException">E_NOTIMPL</exception>
void RequestNewObjectLayout();
}
#endregion
#region IOleObject
/// <summary>
/// This interface is implemented by WMP ActiveX/COM control.
/// The only function we need is <see cref="SetClientSite"/>.
/// </summary>
[ComImport, ComVisible(true),
Guid("00000112-0000-0000-C000-000000000046"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleObject
{
/// <summary>
/// Used to pass our custom <see cref="IOleClientSite"/> object to WMP. The object we pass must also
/// implement <see cref="IOleServiceProvider"/> to work right.
/// </summary>
/// <param name="pClientSite">The <see cref="IOleClientSite"/> to pass.</param>
void SetClientSite(IOleClientSite pClientSite);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
IOleClientSite GetClientSite();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void SetHostNames(
[MarshalAs(UnmanagedType.LPWStr)]string szContainerApp,
[MarshalAs(UnmanagedType.LPWStr)]string szContainerObj);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void Close(uint dwSaveOption);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void SetMoniker(uint dwWhichMoniker, object pmk);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
object GetMoniker(uint dwAssign, uint dwWhichMoniker);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void InitFromData(object pDataObject, bool fCreation, uint dwReserved);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
object GetClipboardData(uint dwReserved);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void DoVerb(uint iVerb, uint lpmsg, [MarshalAs(UnmanagedType.Interface)]object pActiveSite,
uint lindex, uint hwndParent, uint lprcPosRect);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
object EnumVerbs();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void Update();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[PreserveSig]
[return: MarshalAs(UnmanagedType.U4)]
HResults IsUpToDate();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
Guid GetUserClassID();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.LPWStr)]
string GetUserType(uint dwFormOfType);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void SetExtent(uint dwDrawAspect, [MarshalAs(UnmanagedType.Interface)] object psizel);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
object GetExtent(uint dwDrawAspect);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
uint Advise([MarshalAs(UnmanagedType.Interface)]object pAdvSink);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void Unadvise(uint dwConnection);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
[return: MarshalAs(UnmanagedType.Interface)]
object EnumAdvise();
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
uint GetMiscStatus([MarshalAs(UnmanagedType.U4)] DVASPECT dwAspect);
/// <summary>
/// Implemented by Windows Media Player ActiveX control.
/// See MSDN for more information.
/// </summary>
void SetColorScheme([MarshalAs(UnmanagedType.Interface)] object pLogpal);
}
#endregion
}
I have a NativeActivity derived activity that I wrote that is to use bookmarks as a trigger for a pick branch. Using something I found on MSDN I tried writing this to trigger the branch. The branch contains activities that fire service callbacks to remote clients via send activities. If I set a delay for the trigger, callbacks fire to the clients successfully. If I use my code activity, the pick branch activities don't fire.
public sealed class UpdateListener : NativeActivity<ClientUpdate>
{
[RequiredArgument]
public InArgument<string> BookmarkName { get; set; }
protected override void Execute(NativeActivityContext context)
{
context.CreateBookmark(BookmarkName.Get(context),
new BookmarkCallback(this.OnResumeBookmark));
}
protected override bool CanInduceIdle
{
get { return true; }
}
public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj )
{
Result.Set(context, (ClientUpdate)obj);
}
}
So it takes an arg to set the bookmark name for future bookmark references to execute the trigger. OnResumeBoookmark() takes in a ClientUpdate object that is passed by my application that is hosting the workflowapp. The activity is to return the object so the ClientUpdate can be passed to the workflow and have it sent to the remote clients via the send activity in the pick branch. In theory anyways.
For some reason it seems to be correct but feels wrong. I'm not sure if I should write the Activity in a different way to take care of what I need for my WF service.
I think your intentions would be a bit clearer if you created an extension (that implements IWorkflowInstanceExtension) to perform your action here.
For example:
public sealed class AsyncWorkExtension
: IWorkflowInstanceExtension
{
// only one extension per workflow
private WorkflowInstanceProxy _proxy;
private Bookmark _lastBookmark;
/// <summary>
/// Request the extension does some work for an activity
/// during which the activity will idle the workflow
/// </summary>
/// <param name="toResumeMe"></param>
public void DoWork(Bookmark toResumeMe)
{
_lastBookmark = toResumeMe;
// imagine I kick off some async op here
// when complete system calls WorkCompleted below
// NOTE: you CANNOT block here or you block the WF!
}
/// <summary>
/// Called by the system when long-running work is complete
/// </summary>
/// <param name="result"></param>
internal void WorkCompleted(object result)
{
//NOT good practice! example only
//this leaks resources search APM for details
_proxy.BeginResumeBookmark(_lastBookmark, result, null, null);
}
/// <summary>
/// When implemented, returns any additional extensions
/// the implementing class requires.
/// </summary>
/// <returns>
/// A collection of additional workflow extensions.
/// </returns>
IEnumerable<object> IWorkflowInstanceExtension
.GetAdditionalExtensions()
{
return new object[0];
}
/// <summary>
/// Sets the specified target
/// <see cref="WorkflowInstanceProxy"/>.
/// </summary>
/// <param name="instance">The target workflow instance to set.</param>
void IWorkflowInstanceExtension
.SetInstance(WorkflowInstanceProxy instance)
{
_proxy = instance;
}
}
Within the Activity, you'd use this thusly:
var ext = context.GetExtension<AsyncWorkExtension>();
var bookmark = context.CreateBookmark(BookmarkCallback);
ext.DoWork(bookmark);
return;
This way is much more explicit (instead of using the bookmark name to convey meaning to the "outside" world) and is much easier to extend if, say, you require to send out more information than a bookmark name.
Is there something actually resuming the bookmark here? If not the workflow will wait very patiently and nothing will happen.