启动投射设备的投射会话

2023-12-31

我有这个用例:

  1. 检测播放设备并保存其 ID、名称和信息;
  2. 以自动方式连接到预定义设备并开始投射会话 有一些内容。

我研究了 Google Cast API v3,看起来真的很难。在 v2 中,这是可能的,因为发送者应用程序控制了 90% 的进程,即与设备的连接和加载内容,而在 v3 中,会话完全由框架管理,并且仅在用户干预的情况下启动会话。对于我的用例来说,唯一值得的方法是SessionManager.startSession(Intent intent) doc here https://developers.google.com/android/reference/com/google/android/gms/cast/framework/SessionManager.html#startSession(android.content.Intent),但是完全没有记录如何使用意图、额外参数、操作等。有人对这种方法和意图有所了解吗?


太长了;跳至步骤 3 - 选项 1 (SessionManager.startSession) or 步骤 3 - 选项 2 (MediaRouter.selectRoute)

第 1 步 - 设置

像平常一样设置 CastOptionsProvider。

以下是我们将使用的主要对象:

MediaRouter mediaRouter = MediaRouter.getInstance(activity);
CastContext context = CastContext.getSharedInstance(activity);
SessionManager sessionManager = context.getSessionManager();

第 2 步 - 检索路由(设备)以保存/使用

获取路由/设备 ID

步骤 2 - 选项 1 - 当前缓存的路由

只需获取当前缓存的路由:

for (RouteInfo route : mediaRouter.getRoutes()) {
    // Save route.getId(); however you want (it's a string)
}

缺点:返回的路线可能已经过时。 MediaRouter 的路由缓存仅在触发扫描时更新(由您手动或由转换库触发)。

步骤 2 - 选项 2 - 主动扫描

主动扫描最准确的路线列表:

MediaRouter.Callback callback = new MediaRouter.Callback() {
    private void updateMyRouteList() {
        for (RouteInfo route : mediaRouter.getRoutes()) {
            // Save route.getId() however you want (it's a string)
        }
    }
    @Override
    public void onRouteAdded(MediaRouter router, RouteInfo route) {
        updateMyRouteList();
    }

    @Override
    public void onRouteRemoved(MediaRouter router, RouteInfo route) {
        updateMyRouteList();
    }
    @Override
    public void onRouteChanged(MediaRouter router, RouteInfo route) {
        updateMyRouteList();
    }
};
mediaRouter.addCallback(new MediaRouteSelector.Builder()
                .addControlCategory(CastMediaControlIntent.categoryForCast(appId))
                .build(),
        callback,
        MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);

NOTE!停止主动扫描非常重要,否则电池会很快耗尽!您可以使用以下命令停止扫描

mediaRouter.removeCallback(callback);

步骤 2 - 选项 3 - 被动扫描

Same as Option 2但是,省略flags的论证mediaRouter.addCallback.
This should(我认为)被动地监听路线变化。 (虽然你的结果可能不会比Option 1)。例如:

mediaRouter.addCallback(new MediaRouteSelector.Builder()
                .addControlCategory(CastMediaControlIntent.categoryForCast(appId))
                .build(),
        callback);

步骤 3 - 加入路线(设备)

如何以编程方式加入路线(设备)。 有两个主要选项。

这两个选项都会创建一个新会话,或者加入您尝试加入的设备上的现有会话(如果 appId 相同)。

首先,prep:

// Optional - if your app changes receiverApplicationId on the fly you should change that here
context.setReceiverApplicationId(appId);
// Most people would just set this as a constant in their CastOptionsProvider

// Listen for a successful join
sessionManager.addSessionManagerListener(new SessionManagerListener<Session>() {
    @Override
    public void onSessionStarted(CastSession castSession, String sessionId) { 
        // We successfully joined a route(device)!
    }
});

现在,如何实际加入一条路线,给定一个routeId我们从Step 2

步骤 3 - 选项 1 - SessionManager.startSession

注意:我发现此方法在我的 Android 4.4 设备上不起作用。我正在得到SessionManagerListener.onSessionStartFailed错误 15(超时)。
不过它确实可以在我的 Android 7.0 设备上运行。

// Create the intent
Intent castIntent = new Intent();
// Mandatory, if null, nothing will happen
castIntent.putExtra("CAST_INTENT_TO_CAST_ROUTE_ID_KEY", routeId);
// (Optional) Uses this name in the toast
castIntent.putExtra("CAST_INTENT_TO_CAST_DEVICE_NAME_KEY", route.getName());
// Optional - false = displays "Connecting to <devicename>..."
castIntent.putExtra("CAST_INTENT_TO_CAST_NO_TOAST_KEY", true);

sessionManager.startSession(castIntent);

步骤 3 - 选项 2 - MediaRouter.selectRoute

要使用此选项,您必须拥有完整的Route对象,而不仅仅是 id 字符串。
如果您已经拥有该对象,那就太好了!
如果没有的话,你可以使用下面的方法步骤 2 - 选项 2 - 主动扫描得到Route通过查找匹配的 id 来获取对象。

mediaRouter.selectRoute(routeObject);

第 4 步 - 流式传输内容

一旦您获得了会话步骤3准备,艰苦的工作完成了。
您可以使用远程媒体客户端 https://developers.google.com/android/reference/com/google/android/gms/cast/framework/media/RemoteMediaClient控制投射的内容。

RemoteMediaClient remoteMediaClient = castSession.getRemoteMediaClient();
remoteMediaClient.load(...);

完整代码

我之所以将其包括在内,是因为我花了大量的时间来解决会话问题,希望它可以使其他人受益。 (包括 Android 4.4/慢速设备上的间歇性计时和崩溃问题 [不确定哪一个是问题根源])。

那里可能有一些额外的东西(特别是如果你使用常量 appId,initialize无关紧要),所以请使用您需要的。

最相关的方法是selectRoute它接受一个routeId字符串,并会主动扫描匹配最多15秒。它还可以处理一些重试可能有效的错误。

您可以看到真正的完整代码在这里 https://github.com/miloproductionsinc/cordova-plugin-chromecast/blob/master/src/android/ChromecastConnection.java.
[下面的代码可能已过时。真正的完整代码是为在 Cordova 插件中使用而编写的。如果您想在应用程序中使用代码,那么删除 Cordova 依赖项很简单。]

public class ChromecastConnection {

    /** Lifetime variable. */
    private Activity activity;
    /** settings object. */
    private SharedPreferences settings;

    /** Lifetime variable. */
    private SessionListener newConnectionListener;
    /** The Listener callback. */
    private Listener listener;

    /** Initialize lifetime variable. */
    private String appId;

    /**
     * Constructor.  Call this in activity start.
     * @param act the current context
     * @param connectionListener client callbacks for specific events
     */
    ChromecastConnection(Activity act, Listener connectionListener) {
        this.activity = act;
        this.settings = activity.getSharedPreferences("CORDOVA-PLUGIN-CHROMECAST_ChromecastConnection", 0);
        this.appId = settings.getString("appId", CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID);
        this.listener = connectionListener;

        // Set the initial appId
        CastOptionsProvider.setAppId(appId);

        // This is the first call to getContext which will start up the
        // CastContext and prep it for searching for a session to rejoin
        // Also adds the receiver update callback
        getContext().addCastStateListener(listener);
    }

    /**
     * Must be called each time the appId changes and at least once before any other method is called.
     * @param applicationId the app id to use
     * @param callback called when initialization is complete
     */
    public void initialize(String applicationId, CallbackContext callback) {
        activity.runOnUiThread(new Runnable() {
            public void run() {

                // If the app Id changed, set it again
                if (!applicationId.equals(appId)) {
                    setAppId(applicationId);
                }

                // Tell the client that initialization was a success
                callback.success();

                // Check if there is any available receivers for 5 seconds
                startRouteScan(5000L, new ScanCallback() {
                    @Override
                    void onRouteUpdate(List<RouteInfo> routes) {
                        // if the routes have changed, we may have an available device
                        // If there is at least one device available
                        if (getContext().getCastState() != CastState.NO_DEVICES_AVAILABLE) {
                            // Stop the scan
                            stopRouteScan(this);
                            // Let the client know a receiver is available
                            listener.onReceiverAvailableUpdate(true);
                            // Since we have a receiver we may also have an active session
                            CastSession session = getSessionManager().getCurrentCastSession();
                            // If we do have a session
                            if (session != null) {
                                // Let the client know
                                listener.onSessionRejoin(session);
                            }
                        }
                    }
                }, null);
            }
        });
    }

    private MediaRouter getMediaRouter() {
        return MediaRouter.getInstance(activity);
    }

    private CastContext getContext() {
        return CastContext.getSharedInstance(activity);
    }

    private SessionManager getSessionManager() {
        return getContext().getSessionManager();
    }

    private CastSession getSession() {
        return getSessionManager().getCurrentCastSession();
    }

    private void setAppId(String applicationId) {
        this.appId = applicationId;
        this.settings.edit().putString("appId", appId).apply();
        getContext().setReceiverApplicationId(appId);
    }

    /**
     * This will create a new session or seamlessly selectRoute an existing one if we created it.
     * @param routeId the id of the route to selectRoute
     * @param callback calls callback.onJoin when we have joined a session,
     *                 or callback.onError if an error occurred
     */
    public void selectRoute(final String routeId, SelectRouteCallback callback) {
        activity.runOnUiThread(new Runnable() {
            public void run() {
                if (getSession() != null && getSession().isConnected()) {
                    callback.onError(ChromecastUtilities.createError("session_error",
                            "Leave or stop current session before attempting to join new session."));
                }

                // We need this hack so that we can access these values in callbacks without having
                // to store it as a global variable, just always access first element
                final boolean[] foundRoute = {false};
                final boolean[] sentResult = {false};
                final int[] retries = {0};

                // We need to start an active scan because getMediaRouter().getRoutes() may be out
                // of date.  Also, maintaining a list of known routes doesn't work.  It is possible
                // to have a route in your "known" routes list, but is not in
                // getMediaRouter().getRoutes() which will result in "Ignoring attempt to select
                // removed route: ", even if that route *should* be available.  This state could
                // happen because routes are periodically "removed" and "added", and if the last
                // time media router was scanning ended when the route was temporarily removed the
                // getRoutes() fn will have no record of the route.  We need the active scan to
                // avoid this situation as well.  PS. Just running the scan non-stop is a poor idea
                // since it will drain battery power quickly.
                ScanCallback scan = new ScanCallback() {
                    @Override
                    void onRouteUpdate(List<RouteInfo> routes) {
                        // Look for the matching route
                        for (RouteInfo route : routes) {
                            if (!foundRoute[0] && route.getId().equals(routeId)) {
                                // Found the route!
                                foundRoute[0] = true;
                                // try-catch for issue:
                                // https://github.com/jellyfin/cordova-plugin-chromecast/issues/48
                                try {
                                    // Try selecting the route!
                                    getMediaRouter().selectRoute(route);
                                } catch (NullPointerException e) {
                                    // Let it try to find the route again
                                    foundRoute[0] = false;
                                }
                            }
                        }
                    }
                };

                Runnable retry = new Runnable() {
                    @Override
                    public void run() {
                        // Reset foundRoute
                        foundRoute[0] = false;
                        // Feed current routes into scan so that it can retry.
                        // If route is there, it will try to join,
                        // if not, it should wait for the scan to find the route
                        scan.onRouteUpdate(getMediaRouter().getRoutes());
                    }
                };

                Function<JSONObject, Void> sendErrorResult = new Function<JSONObject, Void>() {
                    @Override
                    public Void apply(JSONObject message) {
                        if (!sentResult[0]) {
                            sentResult[0] = true;
                            stopRouteScan(scan);
                            callback.onError(message);
                        }
                        return null;
                    }
                };

                listenForConnection(new ConnectionCallback() {
                    @Override
                    public void onJoin(CastSession session) {
                        sentResult[0] = true;
                        stopRouteScan(scan);
                        callback.onJoin(session);
                    }
                    @Override
                    public boolean onSessionStartFailed(int errorCode) {
                        if (errorCode == 7 || errorCode == 15) {
                            // It network or timeout error retry
                            retry.run();
                            return false;
                        } else {
                            sendErrorResult.apply(ChromecastUtilities.createError("session_error",
                                    "Failed to start session with error code: " + errorCode));
                            return true;
                        }
                    }
                    @Override
                    public boolean onSessionEndedBeforeStart(int errorCode) {
                        if (retries[0] < 10) {
                            retries[0]++;
                            retry.run();
                            return false;
                        } else {
                            sendErrorResult.apply(ChromecastUtilities.createError("session_error",
                                    "Failed to to join existing route (" + routeId + ") " + retries[0] + 1 + " times before giving up."));
                            return true;
                        }
                    }
                });

                startRouteScan(15000L, scan, new Runnable() {
                    @Override
                    public void run() {
                        sendErrorResult.apply(ChromecastUtilities.createError("timeout",
                                "Failed to to join route (" + routeId + ") after 15s and " + retries[0] + 1 + " trys."));
                    }
                });
            }
        });
    }

    /**
     * Must be called from the main thread.
     * @param callback calls callback.success when we have joined, or callback.error if an error occurred
     */
    private void listenForConnection(ConnectionCallback callback) {
        // We should only ever have one of these listeners active at a time, so remove previous
        getSessionManager().removeSessionManagerListener(newConnectionListener, CastSession.class);
        newConnectionListener = new SessionListener() {
            @Override
            public void onSessionStarted(CastSession castSession, String sessionId) {
                getSessionManager().removeSessionManagerListener(this, CastSession.class);
                callback.onJoin(castSession);
            }
            @Override
            public void onSessionStartFailed(CastSession castSession, int errCode) {
                if (callback.onSessionStartFailed(errCode)) {
                    getSessionManager().removeSessionManagerListener(this, CastSession.class);
                }
            }
            @Override
            public void onSessionEnded(CastSession castSession, int errCode) {
                if (callback.onSessionEndedBeforeStart(errCode)) {
                    getSessionManager().removeSessionManagerListener(this, CastSession.class);
                }
            }
        };
        getSessionManager().addSessionManagerListener(newConnectionListener, CastSession.class);
    }

    /**
     * Starts listening for receiver updates.
     * Must call stopRouteScan(callback) or the battery will drain with non-stop active scanning.
     * @param timeout ms until the scan automatically stops,
     *                if 0 only calls callback.onRouteUpdate once with the currently known routes
     *                if null, will scan until stopRouteScan is called
     * @param callback the callback to receive route updates on
     * @param onTimeout called when the timeout hits
     */
    public void startRouteScan(Long timeout, ScanCallback callback, Runnable onTimeout) {
        // Add the callback in active scan mode
        activity.runOnUiThread(new Runnable() {
            public void run() {
                callback.setMediaRouter(getMediaRouter());

                if (timeout != null && timeout == 0) {
                    // Send out the one time routes
                    callback.onFilteredRouteUpdate();
                    return;
                }

                // Add the callback in active scan mode
                getMediaRouter().addCallback(new MediaRouteSelector.Builder()
                        .addControlCategory(CastMediaControlIntent.categoryForCast(appId))
                        .build(),
                        callback,
                        MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);

                // Send out the initial routes after the callback has been added.
                // This is important because if the callback calls stopRouteScan only once, and it
                // happens during this call of "onFilterRouteUpdate", there must actually be an
                // added callback to remove to stop the scan.
                callback.onFilteredRouteUpdate();

                if (timeout != null) {
                    // remove the callback after timeout ms, and notify caller
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            // And stop the scan for routes
                            getMediaRouter().removeCallback(callback);
                            // Notify
                            if (onTimeout != null) {
                                onTimeout.run();
                            }
                        }
                    }, timeout);
                }
            }
        });
    }

    /**
     * Call to stop the active scan if any exist.
     * @param callback the callback to stop and remove
     */
    public void stopRouteScan(ScanCallback callback) {
        activity.runOnUiThread(new Runnable() {
            public void run() {
                callback.stop();
                getMediaRouter().removeCallback(callback);
            }
        });
    }

    /**
     * Create this empty class so that we don't have to override every function
     * each time we need a SessionManagerListener.
     */
    private class SessionListener implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession castSession) { }
        @Override
        public void onSessionStarted(CastSession castSession, String sessionId) { }
        @Override
        public void onSessionStartFailed(CastSession castSession, int error) { }
        @Override
        public void onSessionEnding(CastSession castSession) { }
        @Override
        public void onSessionEnded(CastSession castSession, int error) { }
        @Override
        public void onSessionResuming(CastSession castSession, String sessionId) { }
        @Override
        public void onSessionResumed(CastSession castSession, boolean wasSuspended) { }
        @Override
        public void onSessionResumeFailed(CastSession castSession, int error) { }
        @Override
        public void onSessionSuspended(CastSession castSession, int reason) { }
    }

    interface SelectRouteCallback {
        void onJoin(CastSession session);
        void onError(JSONObject message);
    }

    interface ConnectionCallback {
        /**
         * Successfully joined a session on a route.
         * @param session the session we joined
         */
        void onJoin(CastSession session);

        /**
         * Called if we received an error.
         * @param errorCode You can find the error meaning here:
         *                 https://developers.google.com/android/reference/com/google/android/gms/cast/CastStatusCodes
         * @return true if we are done listening for join, false, if we to keep listening
         */
        boolean onSessionStartFailed(int errorCode);

        /**
         * Called when we detect a session ended event before session started.
         * See issues:
         *     https://github.com/jellyfin/cordova-plugin-chromecast/issues/49
         *     https://github.com/jellyfin/cordova-plugin-chromecast/issues/48
         * @param errorCode error to output
         * @return true if we are done listening for join, false, if we to keep listening
         */
        boolean onSessionEndedBeforeStart(int errorCode);
    }

    public abstract static class ScanCallback extends MediaRouter.Callback {
        /**
         * Called whenever a route is updated.
         * @param routes the currently available routes
         */
        abstract void onRouteUpdate(List<RouteInfo> routes);

        /** records whether we have been stopped or not. */
        private boolean stopped = false;
        /** Global mediaRouter object. */
        private MediaRouter mediaRouter;

        /**
         * Sets the mediaRouter object.
         * @param router mediaRouter object
         */
        void setMediaRouter(MediaRouter router) {
            this.mediaRouter = router;
        }

        /**
         * Call this method when you wish to stop scanning.
         * It is important that it is called, otherwise battery
         * life will drain more quickly.
         */
        void stop() {
            stopped = true;
        }
        private void onFilteredRouteUpdate() {
            if (stopped || mediaRouter == null) {
                return;
            }
            List<RouteInfo> outRoutes = new ArrayList<>();
            // Filter the routes
            for (RouteInfo route : mediaRouter.getRoutes()) {
                // We don't want default routes, or duplicate active routes
                // or multizone duplicates https://github.com/jellyfin/cordova-plugin-chromecast/issues/32
                Bundle extras = route.getExtras();
                if (extras != null) {
                    CastDevice.getFromBundle(extras);
                    if (extras.getString("com.google.android.gms.cast.EXTRA_SESSION_ID") != null) {
                        continue;
                    }
                }
                if (!route.isDefault()
                        && !route.getDescription().equals("Google Cast Multizone Member")
                        && route.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_REMOTE
                ) {
                    outRoutes.add(route);
                }
            }
            onRouteUpdate(outRoutes);
        }
        @Override
        public final void onRouteAdded(MediaRouter router, RouteInfo route) {
            onFilteredRouteUpdate();
        }
        @Override
        public final void onRouteChanged(MediaRouter router, RouteInfo route) {
            onFilteredRouteUpdate();
        }
        @Override
        public final void onRouteRemoved(MediaRouter router, RouteInfo route) {
            onFilteredRouteUpdate();
        }
    }

    abstract static class Listener implements CastStateListener {
        abstract void onReceiverAvailableUpdate(boolean available);
        abstract void onSessionRejoin(CastSession session);

        /** CastStateListener functions. */
        @Override
        public void onCastStateChanged(int state) {
            onReceiverAvailableUpdate(state != CastState.NO_DEVICES_AVAILABLE);
        }
    }

}

使用 chromecast 非常有趣...

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

启动投射设备的投射会话 的相关文章

随机推荐

  • Django - 用多个计数进行注释

    我有一个模型叫Post它有两个字段upvotes and downvotes Now upvotes downvotes are ManyToManyField to a Profile 这是模型 class Post models Mod
  • 跟踪 Erlang 中从邮箱消费消息的操作

    我浏览了文档trace 3Erlang 中的 BIF 然而 我的一个观察结果是它不能用于跟踪邮箱中消息的使用情况 旗帜 receive 仅跟踪消息何时添加到进程的邮箱 有没有一种方法可以跟踪事件 例如使用receive构造 如果不是 是否有
  • 未报告的异常 java.sql.SQLException;必须被抓住还是被宣告被抛出? [复制]

    这个问题在这里已经有答案了 我在尝试编译以下代码时遇到此错误 我想知道我做错了什么 unreported exception java sql SQLException must be caught or declared to be th
  • 如何在Android中正确实现feed(类似于Facebook/Instagram)?

    我对 Android 很陌生 我正在尝试创建一个包含大量图像和一些元数据的社交应用程序 它有一个类似于 Facebook 上的信息流的屏幕 我想让这个屏幕尽可能的平滑和标准 以下是我正在使用的库 OkHttp Picasso Retrofi
  • Xcode 如何找到隐式目标依赖项?

    Xcode 有时会自动查找依赖项 我认为当我是定义关系的人并且当我变得懒惰时 这是可以的 但我经常发现自己面临着一个具有多个目标的现有 中型到大型 项目 由于该项目是由其他人制作的 因此我发现很难理解哪些目标取决于什么并非所有关系都是明确的
  • 如何使用 WCF 连接 Apple 的 GSX NewGeneration Web 服务?

    从 2015 年 8 月 15 日开始 Apple 的 GSX Web 服务将升级到更安全的版本 每个请求都需要客户端 SSL 证书 我需要采取哪些步骤才能使用 WCF 框架和 C NET 连接到这个新一代 Web 服务 Apple 的文档
  • Selenium (Python) - 单击按钮元素但不将页面重定向到目标链接

    我正在 Python 中使用 Selenium 测试 Web UI 我遇到了一个测试用例 其中按钮单击后应重定向到另一个页面 但是 每次代码执行时都没有任何异常 但页面仍然没有被重定向 我确信按钮被正确单击 因为按钮动画和鼠标光标发生变化
  • Excel:使用工作表作为函数?

    我有一个 Excel 工作表 它接受两个输入并生成一个输出 我当前可以打开工作表 将两者键入单元格 A1 和 A2 结果显示在 A3 中 有没有办法可以将其变成函数或子例程 以便我可以在另一个工作表中使用它来填写值表 数据表 http of
  • Code Golf:验证数独网格

    Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的答案或互动 介绍 有效的数独网格由数字 1 到 9 填充 并且数字在 9 行或列的每个子块中出现的次数不会
  • Grails jQuery Mobile 应用程序中的 Spring Security 刷新错误

    我有一个 Grails 2 0 1 jQuery Mobile 应用程序 这是我第一次使用 Spring Security 我遵循了 Peter Ledbrook 的出色指示post http blog springsource org 2
  • 如何将 pg_dump 与连接 uri / url 一起使用?

    我可以调用psql像这样 psql postgres 我该如何使用pg dump带有以下格式的连接字符串postgres 比将 URI 分解为主机 帖子 用户名 密码更方便 有这方面的语法吗 pg dump postgres usernam
  • MySQL 无法在 AMPPS OS X 上启动

    我在使用 AMPPS 启动 mysql 时遇到问题 我正在使用 OS X Mavericks 和最新版本的 Ampps 在小系统崩溃并重新启动后 我无法启动 mysql mysql 错误 2014 01 22 18 12 41 398 No
  • C# 获取机器IP地址的方法

    如何在 C 中获取机器的 IP 地址 IPAddress localIPs Dns GetHostAddresses Dns GetHostName 您的计算机没有单个 IP 地址 并且某些返回的地址可能是 IPv6 MSDN 链接 Dns
  • 获取视频上传的确切时间

    我正在使用Youtube API http gdata python client googlecode com svn trunk pydocs gdata html使用关键字查询进行搜索 import gdata youtube imp
  • ASP.NET MVC3 - “对象引用未设置到对象的实例”错误

    我对 NET 和 MVC3 比较陌生 尝试添加对象的实例时 我遇到了上述错误消息的问题 下面是我的代码 关键日期类 public class KeyDate Key public int KeyID get set StringLength
  • 如何将我的解决方案纳入 Windows 问题报告和解决方案

    Windows Vista 添加了问题报告和解决方案功能 用于记录软件问题 将其报告给 Microsoft 然后表示他们会收集这些问题的解决方案并提供给用户 因此 当我的程序遇到错误并崩溃时 用户会收到异常报告 source beholdg
  • 单击时不要隐藏 OverlayPanel

    我想做 PrimeFaces覆盖面板 http www primefaces org showcase labs ui overlayPanel jsf即使用户单击工具提示之外的区域也保持可见 关闭工具提示的唯一方法是使用其上的 关闭 按钮
  • Apiary:将 API 导出为 JSON,以生成客户端代码

    我们都知道养蜂场很强大 或者不是 我认为确实如此 我想知道如何才能更进一步允许用户导出 API 的 JSON 描述 因此开发人员可以编写客户端代码生成脚本 这应该有帮助 http ttezel github io blog 2013 02
  • c中的序列点

    命令式编程中的序列点定义了计算机程序执行中的任何点 在该点处保证先前评估的所有副作用都已执行 并且尚未执行后续评估的任何副作用 这是什么意思 有人可以用简单的话解释一下吗 当序列点发生时 基本上意味着您可以保证之前的所有操作都已完成 在没有
  • 启动投射设备的投射会话

    我有这个用例 检测播放设备并保存其 ID 名称和信息 以自动方式连接到预定义设备并开始投射会话 有一些内容 我研究了 Google Cast API v3 看起来真的很难 在 v2 中 这是可能的 因为发送者应用程序控制了 90 的进程 即