我试图将Deezer的最后一个原生SDK库用到C#WPF应用程序中播放一首歌(与只允许抓取信息但不播放音乐的JSON库相对)。 不幸的是,我无法通过oAuth令牌进行身份验证,因此无法使其正常工作。我可以获取oAuth令牌,但SDK总是在Connect.Start()调用上返回USER_ACCESS_TOKEN_FAILED,即使我在通过SetAccessToken方法调用之前设置好令牌(通过其网站上的API资源管理器验证)。
有人可以帮我解决这个问题吗?
重现应用程序的步骤: - 从deezer developper网站下载最新的SDK。 - 创建WPF应用程序项目 - 在其属性Build部分中,取消选中Prefer 32 bits并选中Allow unsafe code - 将libdeezer.x64.dll添加到您的项目中,并在构建操作中将其设置为none,并复制操作以在较新时复制。 - 在主视图中,添加webbrowser并将Navigating事件添加到后面的代码中。 - 创建一个类并粘贴此代码(这是本机C ++ / CLI包装器):
using System;
using System.Collections;
using System.Runtime.InteropServices;
// make this binding dependent on WPF, but easier to use
using System.Windows.Threading;
// http://www.codeproject.com/Articles/339290/PInvoke-pointer-safety-Replacing-IntPtr-with-unsaf
namespace Deezer
{
// called with userdata Dispatcher on connect events
public delegate void ConnectOnEventCb(Connect connect, ConnectEvent connectEvent, DispatcherObject userdata);
public delegate void PlayerOnEventCb(Player player, PlayerEvent playerEvent, DispatcherObject userdata);
// to be in sync with dz_connect_configuration
[StructLayout(LayoutKind.Sequential)]
public class ConnectConfig
{
public string ccAppId;
public string ccAppSecret;
public string ccUserProfilePath;
public Dispatcher ccConnectUserdata;
public ConnectOnEventCb ccConnectEventCb;
}
public class ConnectEvent
{
internal CONNECT_EVENT_TYPE eventType;
/* two design strategies:
* - we could keep a reference to CONNECT_EVENT* with dz_object_retain and call method on the fly
* - we extract all info in constructor and have pure managed object
*
* here we keep the second option, because we have to have a managed object anyway, and it's
* a lot fewer unsafe method to expose, even though it's making a lot of calls in the constructor..
*/
public unsafe static ConnectEvent newFromLibcEvent(CONNECT_EVENT* libcConnectEventHndl)
{
CONNECT_EVENT_TYPE eventType;
unsafe
{
eventType = dz_connect_event_get_type(libcConnectEventHndl);
}
switch (eventType)
{
case CONNECT_EVENT_TYPE.USER_ACCESS_TOKEN_OK:
string accessToken;
unsafe
{
IntPtr libcAccessTokenString = dz_connect_event_get_access_token(libcConnectEventHndl);
accessToken = Marshal.PtrToStringAnsi(libcAccessTokenString);
}
return new NewAccessTokenConnectEvent(accessToken);
default:
return new ConnectEvent(eventType);
}
}
public ConnectEvent(CONNECT_EVENT_TYPE eventType)
{
this.eventType = eventType;
}
public CONNECT_EVENT_TYPE GetEventType()
{
return eventType;
}
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe CONNECT_EVENT_TYPE dz_connect_event_get_type(
CONNECT_EVENT* dzConnectEvent);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe IntPtr dz_connect_event_get_access_token(
CONNECT_EVENT* dzConnectEvent);
}
public class NewAccessTokenConnectEvent : ConnectEvent
{
string accessToken;
public NewAccessTokenConnectEvent(string accessToken)
: base(CONNECT_EVENT_TYPE.USER_ACCESS_TOKEN_OK)
{
this.accessToken = accessToken;
}
public string GetAccessToken()
{
return accessToken;
}
}
unsafe public class Connect
{
// hash
static Hashtable refKeeper = new Hashtable();
internal unsafe CONNECT* libcConnectHndl;
internal ConnectConfig connectConfig;
public unsafe Connect(ConnectConfig cc)
{
NativeMethods.LoadClass();
//ConsoleHelper.AllocConsole();
// attach a console to parent process (launch from cmd.exe)
//ConsoleHelper.AttachConsole(-1);
CONNECT_CONFIG libcCc = new CONNECT_CONFIG();
connectConfig = cc;
IntPtr intptr = new IntPtr(this.GetHashCode());
refKeeper[intptr] = this;
libcCc.ccAppId = cc.ccAppId;
libcCc.ccAppSecret = cc.ccAppSecret;
libcCc.ccUserProfilePath = UTF8Marshaler.GetInstance(null).MarshalManagedToNative(cc.ccUserProfilePath);
libcCc.ccConnectEventCb = delegate (CONNECT* libcConnect, CONNECT_EVENT* libcConnectEvent, IntPtr userdata)
{
Connect connect = (Connect)refKeeper[userdata];
ConnectEvent connectEvent = ConnectEvent.newFromLibcEvent(libcConnectEvent);
Dispatcher dispather = connect.connectConfig.ccConnectUserdata;
dispather.Invoke(connect.connectConfig.ccConnectEventCb, connect, connectEvent, connect.connectConfig.ccConnectUserdata);
};
libcConnectHndl = dz_connect_new(libcCc);
UTF8Marshaler.GetInstance(null).CleanUpNativeData(libcCc.ccUserProfilePath);
}
public int Start()
{
int ret;
ret = dz_connect_activate(libcConnectHndl, new IntPtr(this.GetHashCode()));
return ret;
}
public string DeviceId()
{
IntPtr libcDeviceId = dz_connect_get_device_id(libcConnectHndl);
if (libcDeviceId == null)
{
return null;
}
return Marshal.PtrToStringAnsi(libcDeviceId);
}
public int SetAccessToken(string accessToken)
{
int ret;
ret = dz_connect_set_access_token(libcConnectHndl, IntPtr.Zero, IntPtr.Zero, accessToken);
return ret;
}
public int SetSmartCache(string path, int quotaKb)
{
int ret;
ret = dz_connect_cache_path_set(libcConnectHndl, IntPtr.Zero, IntPtr.Zero, path);
ret = dz_connect_smartcache_quota_set(libcConnectHndl, IntPtr.Zero, IntPtr.Zero, quotaKb);
return ret;
}
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe CONNECT* dz_connect_new(
[In, MarshalAs(UnmanagedType.LPStruct)]
CONNECT_CONFIG lpcc);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe IntPtr dz_connect_get_device_id(
CONNECT* dzConnect);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_connect_activate(
CONNECT* dzConnect, IntPtr userdata);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_connect_set_access_token(
CONNECT* dzConnect, IntPtr cb, IntPtr userdata, string access_token);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_connect_cache_path_set(
CONNECT* dzConnect, IntPtr cb, IntPtr userdata,
[MarshalAs(UnmanagedType.CustomMarshaler,
MarshalTypeRef=typeof(UTF8Marshaler))]
string local_path);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_connect_smartcache_quota_set(
CONNECT* dzConnect, IntPtr cb, IntPtr userdata,
int quota_kB);
}
public class PlayerEvent
{
internal PLAYER_EVENT_TYPE eventType;
/* two design strategies:
* - we could keep a reference to PLAYER_EVENT* with dz_object_retain and call method on the fly
* - we extract all info in constructor and have pure managed object
*
* here we keep the second option, because we have to have a managed object anyway, and it's
* a lot fewer unsafe method to expose, even though it's making a lot of calls in the constructor..
*/
public unsafe static PlayerEvent newFromLibcEvent(PLAYER_EVENT* libcPlayerEventHndl)
{
PLAYER_EVENT_TYPE eventType;
unsafe
{
eventType = dz_player_event_get_type(libcPlayerEventHndl);
}
switch (eventType)
{
default:
return new PlayerEvent(eventType);
}
}
public PlayerEvent(PLAYER_EVENT_TYPE eventType)
{
this.eventType = eventType;
}
public PLAYER_EVENT_TYPE GetEventType()
{
return eventType;
}
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe PLAYER_EVENT_TYPE dz_player_event_get_type(
PLAYER_EVENT* dzPlayerEvent);
}
unsafe public class Player
{
// hash
static Hashtable refKeeper = new Hashtable();
internal unsafe PLAYER* libcPlayerHndl;
internal Connect connect;
internal libcPlayerOnEventCb eventcb;
public unsafe Player(Connect connect, object observer)
{
IntPtr intptr = new IntPtr(this.GetHashCode());
refKeeper[intptr] = this;
libcPlayerHndl = dz_player_new(connect.libcConnectHndl, intptr);
this.connect = connect;
}
public int Start(PlayerOnEventCb eventcb)
{
int ret;
ret = dz_player_activate(libcPlayerHndl, new IntPtr(this.GetHashCode()));
this.eventcb = delegate (PLAYER* libcPlayer, PLAYER_EVENT* libcPlayerEvent, IntPtr userdata)
{
Player player = (Player)refKeeper[userdata];
PlayerEvent playerEvent = PlayerEvent.newFromLibcEvent(libcPlayerEvent);
Dispatcher dispather = player.connect.connectConfig.ccConnectUserdata;
dispather.Invoke(eventcb, player, playerEvent, connect.connectConfig.ccConnectUserdata);
};
ret = dz_player_set_event_cb(libcPlayerHndl, this.eventcb);
return ret;
}
public int LoadStream(string url)
{
int ret;
ret = dz_player_load(libcPlayerHndl, IntPtr.Zero, IntPtr.Zero, url);
return ret;
}
public int Play(int idx)
{
int ret;
ret = dz_player_play(libcPlayerHndl, IntPtr.Zero, IntPtr.Zero, idx, TRACKLIST_AUTOPLAY_MODE.ONE);
return ret;
}
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe PLAYER* dz_player_new(CONNECT* lpcc, IntPtr userdata);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_player_set_event_cb(PLAYER* lpcc, libcPlayerOnEventCb cb);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_player_activate(
PLAYER* dzPlayer, IntPtr userdata);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_player_load(
PLAYER* dzPlayer, IntPtr cb, IntPtr userdata, string url);
[DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
static extern unsafe int dz_player_play(
PLAYER* dzPlayer, IntPtr cb, IntPtr userdata, int idx, TRACKLIST_AUTOPLAY_MODE mode);
}
unsafe public struct CONNECT_EVENT { };
public enum CONNECT_EVENT_TYPE
{
USER_OFFLINE_AVAILABLE, /**< */
USER_ACCESS_TOKEN_OK, /**< */
USER_ACCESS_TOKEN_FAILED, /**< */
USER_LOGIN_OK, /**< */
USER_LOGIN_FAIL_NETWORK_ERROR, /**< */
USER_LOGIN_FAIL_BAD_CREDENTIALS, /**< */
USER_LOGIN_FAIL_USER_INFO, /**< */
USER_SYNC_LICENSES_OK, /**< */
USER_SYNC_LICENSES_FAILED, /**< */
LAST, /**< first invalid value for dz_connect_event_t */
};
public enum TRACKLIST_AUTOPLAY_MODE
{
ONE,
ONE_REPEAT,
NEXT,
NEXT_REPEAT,
RANDOM,
RANDOM_REPEAT,
LAST,
};
unsafe public struct UTF8STRING { };
unsafe public struct CONNECT { };
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
unsafe public delegate void libcConnectOnEventCb(CONNECT* libcConnect, CONNECT_EVENT* libcConnectEvent, IntPtr userdata);
[StructLayout(LayoutKind.Sequential)]
public class CONNECT_CONFIG
{
public string ccAppId;
public string ccAppSecret;
public string ccProductId;
public string ccProductBuildId;
//[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]
public IntPtr ccUserProfilePath;
public libcConnectOnEventCb ccConnectEventCb;
}
unsafe public struct PLAYER_EVENT { };
public enum PLAYER_EVENT_TYPE
{
/** data access related event */
LIMITATION_FORCED_PAUSE, /**< another deezer player session was created elsewhere, the player entered pause mode */
/** track selection related event */
PLAYLIST_TRACK_NOT_AVAILABLE_OFFLINE,/**< you're offline, and the track is not available */
PLAYLIST_TRACK_NO_RIGHT, /**< you don't have the right to render this track */
PLAYLIST_SKIP_NO_RIGHT, /**< you're on a radio, and you had no right to do skip */
PLAYLIST_TRACK_SELECTED, /**< a track is selected and is going to be read (radio) */
/** data loading related event */
MEDIASTREAM_DATA_READY, /**< data is ready to be injected into audio output (first data after a play) */
MEDIASTREAM_DATA_READY_AFTER_SEEK, /**< data is ready to be injected into audio output (first data after a seek) */
/** play (audio rendering on output) related event */
RENDER_TRACK_START_FAILURE, /**< error on trying to start a track */
RENDER_TRACK_START, /**< a track start playing */
RENDER_TRACK_END, /**< a track finished playing because of end of stream */
RENDER_TRACK_PAUSED, /**< paused is effective */
RENDER_TRACK_SEEKING, /**< waiting for new data on seek */
RENDER_TRACK_UNDERFLOW, /**< underflow happened when playing a track */
RENDER_TRACK_RESUMED, /**< player resumed play after a underflow or a pause */
RENDER_TRACK_REMOVED, /**< player stopped playing a track */
LAST, /**< first invalid value for dz_player_event_t */
};
unsafe public struct PLAYER { };
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
unsafe public delegate void libcPlayerOnEventCb(PLAYER* libcPlayer, PLAYER_EVENT* libcPlayerEvent, IntPtr userdata);
// trick from http://stackoverflow.com/questions/1573724/cpu-architecture-independent-p-invoke-can-the-dllname-or-path-be-dynamic
// but actually SetDllDirectory works better (for pthread.dll)
public static class NativeMethods
{
// call this to load this class
public static void LoadClass()
{
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SetDllDirectory(string lpPathName);
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr LoadLibrary(string lpFileName);
static NativeMethods()
{
string arch;
string basePath = System.IO.Path.GetDirectoryName(typeof(NativeMethods).Assembly.Location);
if (IntPtr.Size == 4)
arch = "i386";
else
arch = "x86_64";
System.Diagnostics.Debug.WriteLine("using arch: " + arch);
SetDllDirectory(System.IO.Path.Combine(basePath, arch));
#if false // can be used to debug library loading
IntPtr hExe = LoadLibrary("libdeezer.x64.dll");
if (hExe == IntPtr.Zero)
{
Win32Exception ex = new Win32Exception(Marshal.GetLastWin32Error());
System.Console.WriteLine("exception:" + ex);
throw ex;
}
#endif
}
}
// http://stackoverflow.com/questions/10415807/output-console-writeline-from-wpf-windows-applications-to-actual-console
public class ConsoleHelper
{
/// <summary>
/// Allocates a new console for current process.
/// </summary>
[DllImport("kernel32.dll")]
public static extern Boolean AllocConsole();
[DllImport("Kernel32.dll")]
public static extern bool AttachConsole(int processId);
/// <summary>
/// Frees the console.
/// </summary>
[DllImport("kernel32.dll")]
public static extern Boolean FreeConsole();
}
// http://www.codeproject.com/Articles/138614/Advanced-Topics-in-PInvoke-String-Marshaling
public class UTF8Marshaler : ICustomMarshaler
{
static UTF8Marshaler static_instance;
// maybe we could play with WideCharToMultiByte too and avoid Marshal.Copy
// http://stackoverflow.com/questions/537573/how-to-get-intptr-from-byte-in-c-sharp
/*
Byte[] byNewData = null;
iNewDataLen = NativeMethods.WideCharToMultiByte(NativeMethods.CP_UTF8, 0, cc.ccUserProfilePath, -1, null, 0, IntPtr.Zero, IntPtr.Zero);
Console.WriteLine("iNewDataLen:" + iNewDataLen + " len:" + cc.ccUserProfilePath.Length + " ulen:" + iNewDataLen);
byNewData = new Byte[iNewDataLen];
iNewDataLen = NativeMethods.WideCharToMultiByte(NativeMethods.CP_UTF8, 0, cc.ccUserProfilePath, cc.ccUserProfilePath.Length, byNewData, iNewDataLen, IntPtr.Zero, IntPtr.Zero);
libcCc.ccUserProfilePath = Marshal.UnsafeAddrOfPinnedArrayElement(byNewData, 0);
*/
public IntPtr MarshalManagedToNative(object managedObj)
{
if (managedObj == null)
return IntPtr.Zero;
if (!(managedObj is string))
throw new MarshalDirectiveException(
"UTF8Marshaler must be used on a string.");
// not null terminated
byte[] strbuf = System.Text.Encoding.UTF8.GetBytes((string)managedObj);
IntPtr buffer = Marshal.AllocHGlobal(strbuf.Length + 1);
Marshal.Copy(strbuf, 0, buffer, strbuf.Length);
// write the terminating null
Marshal.WriteByte(buffer + strbuf.Length, 0);
return buffer;
}
public unsafe object MarshalNativeToManaged(IntPtr pNativeData)
{
byte* walk = (byte*)pNativeData;
// find the end of the string
while (*walk != 0)
{
walk++;
}
int length = (int)(walk - (byte*)pNativeData);
// should not be null terminated
byte[] strbuf = new byte[length];
// skip the trailing null
Marshal.Copy((IntPtr)pNativeData, strbuf, 0, length);
string data = System.Text.Encoding.UTF8.GetString(strbuf);
return data;
}
public void CleanUpNativeData(IntPtr pNativeData)
{
Marshal.FreeHGlobal(pNativeData);
}
public void CleanUpManagedData(object managedObj)
{
}
public int GetNativeDataSize()
{
return -1;
}
public static ICustomMarshaler GetInstance(string cookie)
{
if (static_instance == null)
{
return static_instance = new UTF8Marshaler();
}
return static_instance;
}
[DllImport("kernel32.dll")]
public static extern int WideCharToMultiByte(uint CodePage, uint dwFlags,
[MarshalAs(UnmanagedType.LPWStr)] string lpWideCharStr, int cchWideChar,
[MarshalAs(UnmanagedType.LPArray)] Byte[] lpMultiByteStr, int cbMultiByte, IntPtr lpDefaultChar,
IntPtr lpUsedDefaultChar);
public const uint CP_UTF8 = 65001;
}
}
然后,将此代码添加到
后面的主视图代码中using System;
using System.Windows;
using Deezer;
using System.Windows.Threading;
using System.Web;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
dWebBrowser.Navigate("https://connect.deezer.com/fr/oauth/auth.php?app_id=your_app_id_here&redirect_uri=http://your_domain_here&perms=basic_access&response_type=token");
}
private void dWebBrowser_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
{
if (e.Uri.ToString().Contains("replace with your domain") && e.Uri.ToString().Contains("access_token"))
{
e.Cancel = true;
String token = HttpUtility.ParseQueryString(e.Uri.ToString().Replace("#", "&")).Get("access_token");
PlaySong(token);
}
}
private void PlaySong(String token)
{
try
{
ConnectConfig dConfig = new ConnectConfig();
dConfig.ccAppId = "replace with your APP ID";
dConfig.ccAppSecret = "replace with your SECRET APP CODE";
// dConfig.ccConnectEventCb = dConnectOnEventCb; (optional)
dConfig.ccConnectUserdata = this.Dispatcher;
dConfig.ccUserProfilePath = @"D:\dztemp";
Connect dConnect = new Connect(dConfig);
dConnect.SetAccessToken(token);
dConnect.SetSmartCache(@"D:\dztemp", 2000000);
CONNECT_EVENT_TYPE resp = (CONNECT_EVENT_TYPE)dConnect.Start();
String devId = dConnect.DeviceId();
Object dObserver = null;
Player dPlayer = new Player(dConnect, dObserver);
PLAYER_EVENT_TYPE respPlayerStart = (PLAYER_EVENT_TYPE)dPlayer.Start(dPlayerOnEventCb);
PLAYER_EVENT_TYPE respPlayerLoad = (PLAYER_EVENT_TYPE)dPlayer.LoadStream("dzmedia:///track/3135556");
PLAYER_EVENT_TYPE respPlayerPlay = (PLAYER_EVENT_TYPE)dPlayer.Play(0);
}
catch (Exception ex)
{
throw ex;
}
}
public void dConnectOnEventCb(Connect connect, ConnectEvent connectEvent, DispatcherObject userdata)
{
this.Dispatcher.Invoke(connect.connectConfig.ccConnectEventCb, connect, connectEvent, connect.connectConfig.ccConnectUserdata);
}
public void dPlayerOnEventCb(Player player, PlayerEvent playerEvent, DispatcherObject userdata)
{
String a = "";
}
}
}
然后启动您的应用。 然后由(CONNECT_EVENT_TYPE)返回的CONNECT_EVENT_TYPE dConnect.Start()应该是USER_ACCESS_TOKEN_OK,但我总是得到USER_ACCESS_TOKEN_FAILED。 尝试拦截生成的令牌,并通过deezer API浏览器检查它是否是有效令牌。应该没问题。所以我真的不明白这里发生了什么......
答案 0 :(得分:0)
为了确定,你有没有替换:
dConfig.ccAppId = "replace with your APP ID";
dConfig.ccAppSecret = "replace with your SECRET APP CODE";
使用 应用程序ID 并 应用程序密钥 ?
dWebBrowser.Navigate("https://connect.deezer.com/fr/oauth/auth.php?app_id=your_app_id_here&redirect_uri=http://your_domain_here&perms=basic_access&response_type=token");
使用 app_id 和 redirect_uri
修改强>
由于您拥有C#包装器的版本,因此enum稍有改动。 请检查您在包装中使用的所有枚举。例如:
dz_connect_event_t
似乎与C#CONNECT_EVENT_TYPE
请同时查看PLAYER_EVENT_TYPE
枚举,CONNECT_CONFIG
结构,...
CONNECT_CONFIG
应该更像:
public class CONNECT_CONFIG
{
public string ccAppId;
public string ccProductId;
public string ccProductBuildId;
public IntPtr ccUserProfilePath;
public libcConnectOnEventCb ccConnectEventCb;
public string ccAnonymousBlob;
public libcAppCrashDelegate ccAppCrashDelegate;
}