private static void Process(int iLookAheadHours) { //Get the CMIS timetable for the next ?? hours IEnumerable <DataRow> dtTimetable = GetTimetable(iLookAheadHours); //Iterate through each event in the timetable foreach (DataRow drEvent in dtTimetable) { //Try/Catch. If this particular event has problems, we probably want the loop to continue try { //Firstly, check there is a remote recorder in this location RemoteRecorderManagement.RemoteRecorder rRecorder = GetRecorder(drEvent.Field <string>("RoomId")); //If we have a recorder, then we can record this room/event if (rRecorder != null) { //Get/create the folder for this recording SessionManagement.Folder fFolder = GetFolder(drEvent.Field <string>("LecturerIds")); //Ensure that the owner of this recording has access to said folder //The data from CMIS stores multiple lecturers as CSV data inside the LecturerIds field. eg. LECT001, LECT002, LECT003 //Ideally, we would split this into an array and ensure that each lecturer had creator access on the folder foreach (string sUser in drEvent.Field <string>("LecturerIds").Split(',')) { //Another try/catch block - Panopto will throw an exception if user doesn't exist or already has permission try { //Get/create the user UserManagement.User uUser = GetUser(sUser); //Create AccessManagement Client AccessManagement.AccessManagementClient AMC = new AccessManagement.AccessManagementClient(); //Give user permission on our folder AMC.GrantUsersAccessToFolder(AccessAuthentication(), fFolder.Id, new Guid[] { uUser.UserId }, AccessManagement.AccessRole.Creator); } catch (Exception ex) { //Do nothing } } //Now, we schedule the recording //Create the RemoteRecorderManagement client RemoteRecorderManagement.RemoteRecorderManagementClient RMC = new RemoteRecorderManagement.RemoteRecorderManagementClient(); //Name this recording string sName = drEvent.Field <string>("ModuleIds") + " - " + drEvent.Field <string>("RoomId") + " - " + drEvent.Field <DateTime>("StartTime"); //Module, Location, Time //Build the RemoteRecorderSettings object: RemoteRecorderManagement.RecorderSettings RecorderSetting = new RemoteRecorderManagement.RecorderSettings() { RecorderId = rRecorder.Id }; //Schedule the recording //Note that we trim 1 minute from the end of the recording to give the remoterecorder time to recover between back-to-back recordings RemoteRecorderManagement.ScheduledRecordingResult RemoteRecorderResult = RMC.ScheduleRecording(RemoteRecorderAuthentication(), sName, fFolder.Id, false, drEvent.Field <DateTime>("StartTime").ToUniversalTime(), drEvent.Field <DateTime>("StartTime").ToUniversalTime().AddMinutes(-1), new RemoteRecorderManagement.RecorderSettings[] { RecorderSetting }); //Check that the event was scheduled properly if (!RemoteRecorderResult.ConflictsExist) { //Nothing clashed //We're only adding a single schedule, so can grab first DeliveryID Guid gDeliveryIGuid = RemoteRecorderResult.SessionIDs.FirstOrDefault(); //At this point you could store the Guid in a database somewhere, to keep track of Panopto sessions and CMIS events } else { //The schedule was not created because it clashes with an existing schedule on this remote recorder //Our new schedule has not been added to Panopto //RemoteRecorderResult.ConflictingSessions will tell us the DeliveryIDs of the existing sessions } } ; } catch (Exception ex) { //Something happened whilst retrieving the remoterecorder, creating the folder and user, assigning permissions, or scheduling the recording //Deal with this problem as you deem appropriate } } }
//A quick note on the following methods: //These all assume that you are adding 1-2 events from CMIS to Panopto. //It's likely that in the real-world, you'll be adding 100+ events in one go. This can have pretty horrific performance issues with all the required API calls. //In a lot of cases it is far easier to cache some lookups locally and refer to them instead (eg. a list of remote recorders). The information is relatively static and this is safe to do. //None of this example code uses caching. /// <summary> /// Finds a Panopto Remote Recorder by name (description) /// </summary> private static RemoteRecorderManagement.RemoteRecorder GetRecorder(string sLocation) { //Local variables string sRemoteRecorderName; //This method could be used to translate between rooms and remote recorder names. //To keep things simple in this example, we'll assume that the remote recorder name is the same as the room name (eg. remote recorder 'LTB06' is located in room 'LTB06') //But there are several different ways to handle remote recorder names: //- Name your remote recorders after the location they are installed in (whcih is what we'll do here) //- Use a lookup table to determine where your remote recorders are located (University of Essex does this) //- Use the ExternalId property of the RemoteRecorder object to store a custom ID for each recorder //LOOKUP CODE GOES HERE (as above, for brevity we'll assume the remote recorder's name (description) is the same as its location sRemoteRecorderName = sLocation; //Pagination isn't strictly necessary as we've increased maxReceivedMessageSize="6553560" in the app.config //But we'll keep the pagination code for sake of completeness RemoteRecorderManagement.Pagination RemoteRecorderPagination = new RemoteRecorderManagement.Pagination() { MaxNumberResults = iResultsPerPage, PageNumber = 0 }; //Create a Remote Recorder Management Client RemoteRecorderManagement.RemoteRecorderManagementClient RemoteRecorderClient = new RemoteRecorderManagement.RemoteRecorderManagementClient(); //Lookup all Remote Recorders //Note: if you had stored the RecorderId or ExternalId elsewhere, you could use the RemoteRecorderClient.GetRecordersById or RemoteRecorderClient.GetRecordersByExternalID methods to return a single recorder //Note 2: you could potentially cache this response in a global object and check/use it in subsequent calls to speed up the code RemoteRecorderManagement.ListRecordersResponse RemoteRecorderResponse = RemoteRecorderClient.ListRecorders(RemoteRecorderAuthentication(), RemoteRecorderPagination, RemoteRecorderManagement.RecorderSortField.Name); #region ListRecorders handling //Loop through the returned pages, add each result to a List //If we wanted to speed up this code we could potentially stop at the first match, but that carries risk of ignoring duplicate remote recorders List <RemoteRecorderManagement.RemoteRecorder> lRecorders = new List <RemoteRecorderManagement.RemoteRecorder>(); //Keep track of the number of items remaining in the RemoteRecorderResponse queue int iTotalPages = (int)Math.Ceiling(((double)RemoteRecorderResponse.TotalResultCount / (double)iResultsPerPage)); //Total number of pages (total items/items per page) int iCurrentPage = 0; while (iCurrentPage < iTotalPages) { //Increment the page number iCurrentPage++; //Grab results from this age and add to list lRecorders.AddRange(RemoteRecorderResponse.PagedResults.AsEnumerable()); //If we still have items outstanding, we should grab the next page if (iCurrentPage < iTotalPages) { RemoteRecorderPagination = new RemoteRecorderManagement.Pagination() { //Keep the same number of items per page as before MaxNumberResults = iResultsPerPage, PageNumber = iCurrentPage }; //Grab the next page from Panopto's API RemoteRecorderResponse = RemoteRecorderClient.ListRecorders(RemoteRecorderAuthentication(), RemoteRecorderPagination, RemoteRecorderManagement.RecorderSortField.Name); } ; } //Narrow down our list of recorders to match the one we're after lRecorders = lRecorders.Where(r => r.Name.ToLower().Trim() == sRemoteRecorderName.Trim().ToLower()).ToList(); //Duplicate matches? if (lRecorders.Count > 1) { throw new Exception("More than one remote recorder found: " + sRemoteRecorderName); } else if (lRecorders.Count == 0) { return(null); } else { return(lRecorders.FirstOrDefault()); } #endregion }