C++deezer库的C#包装器:PLAYLIST_TRACK_NO_RIGHT

C# wrapper for C++ deezer library : PLAYLIST_TRACK_NO_RIGHT

本文关键字:TRACK NO RIGHT PLAYLIST 库的 包装 C++deezer      更新时间:2023-10-16

我目前正在尝试为本机库制作deezer包装器。它在尝试播放曲目之前一直有效:我收到一个回调,说PLAYLIST_track_NO_RIGHT。

我试着在deezer网站上读过这首歌,效果很好。我还尝试了在deezer网站上运行的另一首曲目,但仍然无法使用包装器,说没有播放权限。

有人知道为什么会出现这个错误吗?我认为Connect方法有问题。我看到ccappSecret属性消失了,取而代之的是弹出的ProductId、ProductBuildId和anonymousblob。

那么,我们必须在这里面写些什么呢?没有关于这方面的文档(我们能找到的唯一文档说我们应该将产品id发送到产品id中。好吧,但这是什么?我们在哪里找到这些信息?)

这是包装代码:

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
{
    #region Enums
    public enum CONNECT_EVENT_TYPE
    {
        UNKNOWN,                           /**< Connect event has not been set yet, not a valid value. */
        USER_OFFLINE_AVAILABLE,            /**< User logged in, and credentials from offline store are loaded. */
        USER_ACCESS_TOKEN_OK,              /**< (Not available) dz_connect_login_with_email() ok, and access_token is available */
        USER_ACCESS_TOKEN_FAILED,          /**< (Not available) dz_connect_login_with_email() failed */
        USER_LOGIN_OK,                     /**< Login with access_token ok, infos from user available. */
        USER_LOGIN_FAIL_NETWORK_ERROR,     /**< Login with access_token failed because of network condition. */
        USER_LOGIN_FAIL_BAD_CREDENTIALS,   /**< Login with access_token failed because of bad credentials. */
        USER_LOGIN_FAIL_USER_INFO,         /**< Login with access_token failed because of other problem. */
        USER_LOGIN_FAIL_OFFLINE_MODE,      /**< Login with access_token failed because we are in forced offline mode. */
        USER_NEW_OPTIONS,                  /**< User options have just changed. */
        ADVERTISEMENT_START,               /**< A new advertisement needs to be displayed. */
        ADVERTISEMENT_STOP,                /**< An advertisement needs to be stopped. */
    };
    public enum ERRORS
    {
        DZ_ERROR_NO_ERROR = 0x00000000,
        DZ_ERROR_NO_ERROR_ASYNC = 0x00000001,
        DZ_ERROR_ERROR_ARG = 0x00000002,
        DZ_ERROR_ERROR_STATE = 0x00000003,
        DZ_ERROR_NOT_IMPLEMENTED = 0x00000004,
        DZ_ERROR_ASYNC_CANCELED = 0x00000005,
        DZ_ERROR_NOT_ENOUGH_MEMORY,
        DZ_ERROR_OS_ERROR,
        DZ_ERROR_UNSUPPORTED,
        DZ_ERROR_CLASS_NOT_FOUND,
        DZ_ERROR_JSON_PARSING,
        DZ_ERROR_XML_PARSING,
        DZ_ERROR_PARSING,
        DZ_ERROR_CLASS_INSTANTIATION,
        DZ_ERROR_RUNNABLE_ALREADY_STARTED,
        DZ_ERROR_RUNNABLE_NOT_STARTED,
        DZ_ERROR_CACHE_RESOURCE_OPEN_FAILED,
        DZ_ERROR_FS_FULL,
        DZ_ERROR_FILE_EXISTS,
        DZ_ERROR_IO_ERROR,
        DZ_ERROR_CATEGORY_CONNECT = 0x00010000,
        DZ_ERROR_CONNECT_SESSION_LOGIN_FAILED,
        DZ_ERROR_USER_PROFILE_PERM_DENIED,
        DZ_ERROR_CACHE_DIRECTORY_PERM_DENIED,
        DZ_ERROR_CONNECT_SESSION_NOT_ONLINE,
        DZ_ERROR_CONNECT_SESSION_OFFLINE_MODE,
        DZ_ERROR_CONNECT_NO_OFFLINE_CACHE,
        DZ_ERROR_CATEGORY_PLAYER = 0x00020000,
        DZ_ERROR_PLAYER_PLAYLIST_NONE_SET,
        DZ_ERROR_PLAYER_PLAYLIST_BAD_INDEX,
        DZ_ERROR_PLAYER_PLAYLIST_NO_MEDIA,         /**< when trying to access non existing track/radio */
        DZ_ERROR_PLAYER_PLAYLIST_NO_RIGHTS,        /**< when trying to access track/radio with no rights */
        DZ_ERROR_PLAYER_PLAYLIST_RIGHT_TIMEOUT,    /**< when timoeout trying to get rights */
        DZ_ERROR_PLAYER_PLAYLIST_RADIO_TOO_MANY_SKIP,
        DZ_ERROR_PLAYER_PLAYLIST_NO_MORE_TRACK,
        DZ_ERROR_PLAYER_PAUSE_NOT_STARTED,
        DZ_ERROR_PLAYER_PAUSE_ALREADY_PAUSED,
        DZ_ERROR_PLAYER_UNPAUSE_NOT_STARTED,
        DZ_ERROR_PLAYER_UNPAUSE_NOT_PAUSED,
        DZ_ERROR_PLAYER_SEEK_NOT_SEEKABLE_NOT_STARTED,
        DZ_ERROR_PLAYER_SEEK_NOT_SEEKABLE_NO_DURATION,
        DZ_ERROR_PLAYER_SEEK_NOT_SEEKABLE_NOT_INDEXED,
        DZ_ERROR_PLAYER_SEEK_NOT_SEEKABLE,
        DZ_ERROR_CATEGORY_MEDIASTREAMER = 0x00030000,
        DZ_ERROR_MEDIASTREAMER_BAD_URL_SCHEME,
        DZ_ERROR_MEDIASTREAMER_BAD_URL_HOST,
        DZ_ERROR_MEDIASTREAMER_BAD_URL_TRACK,
        DZ_ERROR_MEDIASTREAMER_NOT_AVAILABLE_OFFLINE,
        DZ_ERROR_MEDIASTREAMER_NOT_READABLE,
        DZ_ERROR_MEDIASTREAMER_NO_DURATION,
        DZ_ERROR_MEDIASTREAMER_NOT_INDEXED,
        DZ_ERROR_MEDIASTREAMER_SEEK_NOT_SEEKABLE,
        DZ_ERROR_MEDIASTREAMER_NO_DATA,
        DZ_ERROR_MEDIASTREAMER_END_OF_STREAM,
        DZ_ERROR_MEDIASTREAMER_ALREADY_MAPPED,
        DZ_ERROR_MEDIASTREAMER_NOT_MAPPED,
        DZ_ERROR_CATEGORY_OFFLINE = 0x00040000,
        DZ_ERROR_OFFLINE_FS_FULL,
        DZ_ERROR_PLAYER_BAD_URL,
    };
    public enum PLAYER_COMMANDS
    {
        UNKNOWN,           /**< Player command has not been set yet, not a valid value. */
        START_TRACKLIST,   /**< A new tracklist was loaded and a track played. */
        JUMP_IN_TRACKLIST, /**< The user jump into a new song in the current tracklist. */
        NEXT,              /**< Next button. */
        PREV,              /**< Prev button. */
        DISLIKE,           /**< Dislike button. */
        NATURAL_END,       /**< Natural end. */
        RESUMED_AFTER_ADS, /**< Reload after playing an ads. */
    }
    public enum TRACKLIST_AUTOPLAY_MODE
    {
        MODE_UNKNOWN,
        MANUAL,
        MODE_ONE,
        MODE_ONE_REPEAT,
        MODE_NEXT,
        MODE_NEXT_REPEAT,
        MODE_RANDOM,
        MODE_RANDOM_REPEAT,
    };
    public enum PLAYER_EVENT_TYPE
    {
        UNKNOWN,                             /**< Player event has not been set yet, not a valid value. */
        // Data access related event.
        LIMITATION_FORCED_PAUSE,             /**< Another deezer player session was created elsewhere, the player has entered pause mode. */
        // Track selection related event.
        PLAYLIST_TRACK_NOT_AVAILABLE_OFFLINE,/**< You're offline, the track is not available. */
        PLAYLIST_TRACK_NO_RIGHT,             /**< You don't have the right to render this track. */
        PLAYLIST_TRACK_RIGHTS_AFTER_AUDIOADS,/**< You have right to play it, but you should render an ads first :
                                                  - Use dz_player_event_get_advertisement_infos_json().
                                                  - Play an ad with dz_player_play_audioads().
                                                  - Wait for #DZ_PLAYER_EVENT_RENDER_TRACK_END.
                                                  - Use dz_player_play() with previous track or DZ_PLAYER_PLAY_CMD_RESUMED_AFTER_ADS (to be done even on radios for now).
                                              */
        PLAYLIST_SKIP_NO_RIGHT,              /**< You're on a radio, and you had no right to do skip. */
        PLAYLIST_TRACK_SELECTED,             /**< A track is selected among the ones available on the server, and will be fetched and read. */
        PLAYLIST_NEED_NATURAL_NEXT,          /**< We need a new natural_next action. */
        // Data loading related event.
        MEDIASTREAM_DATA_READY,              /**< Data is ready to be introduced into audio output (first data after a play). */
        MEDIASTREAM_DATA_READY_AFTER_SEEK,   /**< Data is ready to be introduced into audio output (first data after a seek). */
        // Play (audio rendering on output) related event.
        RENDER_TRACK_START_FAILURE,       /**< Error, track is unable to play. */
        RENDER_TRACK_START,               /**< A track has started to play. */
        RENDER_TRACK_END,                 /**< A track has stopped because the stream has ended. */
        RENDER_TRACK_PAUSED,              /**< Currently on paused. */
        RENDER_TRACK_SEEKING,             /**< Waiting for new data on seek. */
        RENDER_TRACK_UNDERFLOW,           /**< Underflow happened whilst playing a track. */
        RENDER_TRACK_RESUMED,             /**< Player resumed play after a underflow or a pause. */
        RENDER_TRACK_REMOVED,             /**< Player stopped playing a track. */
    };
    #endregion
    #region Delegates
    // 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);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    unsafe public delegate void libcConnectOnEventCb(CONNECT* libcConnect, CONNECT_EVENT* libcConnectEvent, IntPtr userdata);
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    unsafe public delegate bool libcAppCrashDelegate();
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    unsafe public delegate void libcPlayerOnEventCb(PLAYER* libcPlayer, PLAYER_EVENT* libcPlayerEvent, IntPtr userdata);
    #endregion
    #region Structures
unsafe public struct CONNECT_EVENT { };
unsafe public struct UTF8STRING { };
unsafe public struct CONNECT { };
unsafe public struct PLAYER_EVENT { };
unsafe public struct PLAYER { };
#endregion
    #region Imports
#endregion
    // to be in sync with dz_connect_configuration
    [StructLayout(LayoutKind.Sequential)]
    public class ConnectConfig
    {
        public string ccAppId;
        public string product_id;
        public string product_build_id;
        public string anonymousblob;

        //public string ccAppSecret;
        public string ccUserProfilePath;
        public DispatcherObject 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.ccAnonymousBlob = cc.anonymousblob;
            //libcCc.ccAppSecret = cc.ccAppSecret;
            libcCc.ccProductBuildId = cc.product_build_id;
            libcCc.ccProductId = cc.product_id;
            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);
                DispatcherObject dispather = connect.connectConfig.ccConnectUserdata;
                dispather.Dispatcher.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);
        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);
                DispatcherObject dispather = player.connect.connectConfig.ccConnectUserdata;
                dispather.Dispatcher.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, PLAYER_COMMANDS cmd)
        {
            int ret;
            ret = dz_player_play(libcPlayerHndl, IntPtr.Zero, IntPtr.Zero, cmd, TRACKLIST_AUTOPLAY_MODE.MANUAL, idx);
            //ret = dz_player_play(libcPlayerHndl, IntPtr.Zero, IntPtr.Zero, cmd, TRACKLIST_AUTOPLAY_MODE.MODE_ONE, idx);
            return ret;
        }
        [DllImport("libdeezer.x64.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern unsafe PLAYER* dz_player_new(CONNECT* lpcc);
        //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);
        //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, PLAYER_COMMANDS cmd, TRACKLIST_AUTOPLAY_MODE mode, int idx);
        //static extern unsafe int dz_player_play(PLAYER* dzPlayer, IntPtr cb, IntPtr userdata, int idx, TRACKLIST_AUTOPLAY_MODE mode);
    }

    [StructLayout(LayoutKind.Sequential)]
    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;
    }


    // 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;
    }
}

我遇到了同样的问题

尝试首先运行dz_connect_offline_mode,最后一个参数为false

也许您的access_token不再有效?您能否检查您创建的"access_token"在调用https://connect.deezer.com/oauth/auth.php?app_id=YOUR_APP_ID&redirect_uri=YOUR_REDIRECT_URI&perms=basic_access,email,offline_access时是否启用了offline_access。它应该给予更永久的访问权。(cf:http://developers.deezer.com/api/oauth和http://developers.deezer.com/api/permissions)