private static IAlexaSession UpdateSessionPaging(IAlexaSession session, Properties <MediaItem> properties, bool?isBack = null) { if (isBack == true) { session.paging.pages.Remove(session.paging.currentPage); session.paging.currentPage -= 1; if (session.paging.pages.Count <= 1) { session.paging.canGoBack = false; } return(session); } if (session.paging.pages.Count == 0) { //set the pages dictionary with page 1 session.paging.currentPage = 1; session.paging.pages.Add(session.paging.currentPage, properties); return(session); } if (!session.paging.pages.ContainsValue(properties)) { session.paging.currentPage += 1; session.paging.canGoBack = true; session.paging.pages.Add(session.paging.currentPage, properties); return(session); } return(session); }
protected static void PlayNextUpEpisode(StringBuilder speech, BaseItem item, IAlexaSession session) { speech.Append("Playing the next up episode for "); speech.Append(item.Parent.Parent.Name); speech.Append("Showing in the "); speech.Append(session.room.Name); }
protected static void VoiceAuthenticationExists(StringBuilder speech, IAlexaSession session) { speech.Append(GetSpeechPrefix(SpeechPrefix.DYSFLUENCY_NEGATIVE)); speech.Append(GetSpeechPrefix(SpeechPrefix.APOLOGETIC)); speech.Append(InsertStrengthBreak(StrengthBreak.strong)); speech.Append("This profile is already linked to an account."); speech.Append(SayName(session.context.System.person)); speech.Append("'s account"); }
public async Task <string> Response(IAlexaRequest alexaRequest, IAlexaSession session) { session.room = await RoomContextManager.Instance.ValidateRoom(alexaRequest, session); session.hasRoom = session.room != null; if (!session.hasRoom || !session.hasRoom && !session.supportsApl) //Still go if we have a room, but we don't support APL { session.PersistedRequestData = alexaRequest; AlexaSessionManager.Instance.UpdateSession(session, null); return(await RoomContextManager.Instance.RequestRoom(alexaRequest, session)); } var libraryId = ServerDataQuery.Instance.GetLibraryId(LibraryName); var result = ServerDataQuery.Instance.GetItemById(libraryId); try { #pragma warning disable 4014 Task.Run(() => ServerController.Instance.BrowseItemAsync(session, result)).ConfigureAwait(false); #pragma warning restore 4014 } catch (BrowseCommandException) { throw new BrowseCommandException($"Couldn't browse to {result.Name}"); } session.NowViewingBaseItem = result; //reset rePrompt data because we have fulfilled the request session.PersistedRequestData = null; AlexaSessionManager.Instance.UpdateSession(session, null); var genericLayoutProperties = await DataSourcePropertiesManager.Instance.GetGenericViewPropertiesAsync($"Showing the {result.Name} library", "/MoviesLibrary"); var aplaDataSource = await DataSourcePropertiesManager.Instance.GetAudioResponsePropertiesAsync(new InternalAudioResponseQuery() { SpeechResponseType = SpeechResponseType.ItemBrowse, item = result, session = session }); var renderDocumentDirective = await RenderDocumentDirectiveManager.Instance.RenderVisualDocumentDirectiveAsync(genericLayoutProperties, session); var renderAudioDirective = await RenderDocumentDirectiveManager.Instance.RenderAudioDocumentDirectiveAsync(aplaDataSource); return(await AlexaResponseClient.Instance.BuildAlexaResponseAsync(new Response() { shouldEndSession = null, directives = new List <IDirective>() { renderDocumentDirective, renderAudioDirective } }, session)); }
public async Task <Room> ValidateRoom(IAlexaRequest alexaRequest, IAlexaSession session) { ServerController.Instance.Log.Info("Validating Intent Request Room data."); var request = alexaRequest.request; var intent = request.intent; var slots = intent.slots; var config = Plugin.Instance.Configuration; //Already have a room data saved for the session ServerController.Instance.Log.Info("Checking Session Room Data."); if (!(session.room is null)) { ServerController.Instance.Log.Info("Session Already Contains Room Data. Returning Room Data."); return(session.room); } //Is a user event (button press) if (!(request.arguments is null)) { return(!HasRoomConfiguration(request.arguments[1], config) ? null : config.Rooms.FirstOrDefault(r => string.Equals(r.Name, request.arguments[1], StringComparison.CurrentCultureIgnoreCase))); } //Room's not mentioned in request ServerController.Instance.Log.Info("Checking Intent Request Room Data."); if (string.IsNullOrEmpty(slots.Room.value)) { ServerController.Instance.Log.Info("Intent does not mention a room."); return(null); } if (!HasRoomConfiguration(slots.Room.value, config)) { return(null); } var room = config.Rooms.FirstOrDefault(r => string.Equals(r.Name, slots.Room.value, StringComparison.CurrentCultureIgnoreCase)); var openEmbySessions = ServerDataQuery.Instance.GetCurrentSessions().ToList(); //device needs to be on, and emby must be open and ready for commands if (openEmbySessions.Exists(s => string.Equals(s.DeviceName, room?.DeviceName, StringComparison.CurrentCultureIgnoreCase))) { return(room); } //Update the user about the unfortunate state of the emby device being off. await AlexaResponseClient.Instance.PostProgressiveResponse($"Sorry. Access to the {room?.Name} device is currently unavailable.", alexaRequest.context.System.apiAccessToken, alexaRequest.request.requestId).ConfigureAwait(false); return(null); }
private string GetCorrespondingEmbySessionId(IAlexaSession sessionInfo) { //There is an ID if (!string.IsNullOrEmpty(sessionInfo.EmbySessionId)) { //Is the ID still an active Emby Session if (SessionManager.Sessions.ToList().Exists(s => s.Id == sessionInfo.EmbySessionId)) { return(sessionInfo.EmbySessionId); //ID is still good. TODO:Maybe check the device name is still proper. } return(SessionManager.Sessions.FirstOrDefault(s => s.DeviceName == sessionInfo.room.DeviceName)?.Id); } return(sessionInfo.room is null ? string.Empty : SessionManager.Sessions.FirstOrDefault(s => s.DeviceName == sessionInfo.room.DeviceName)?.Id); }
public async Task PlayMediaItemAsync(IAlexaSession alexaSession, BaseItem item, long?startPositionTicks = null) { var deviceId = ServerDataQuery.Instance.GetDeviceIdFromRoomName(alexaSession.room.Name); if (string.IsNullOrEmpty(deviceId)) { throw new DeviceUnavailableException($"{alexaSession.room.Name} device is currently not available."); } var session = ServerDataQuery.Instance.GetSession(deviceId); if (session is null) { throw new DeviceUnavailableException($"{alexaSession.room.Name} device is currently not available."); } // ReSharper disable once TooManyChainedReferences long startTicks = 0; if (startPositionTicks is null) { if (item.SupportsPlayedStatus(new LibraryOptions() { MinResumeDurationSeconds = 50 })) { startTicks = item.PlaybackPositionTicks; } } else { startTicks = startPositionTicks.Value; } try { await SessionManager.SendPlayCommand(null, session.Id, new PlayRequest { StartPositionTicks = startTicks, PlayCommand = PlayCommand.PlayNow, ItemIds = new[] { item.InternalId }, ControllingUserId = alexaSession.User.Id.ToString() }, CancellationToken.None); } catch (Exception) { throw new PlaybackCommandException($"I had a problem playing {item.Name}."); } }
//TODO: data source object can be null IDataSource dataSource = null in params public void UpdateSession(IAlexaSession session, Properties <MediaItem> properties, bool?isBack = null) { if (!(properties is null)) { session = UpdateSessionPaging(session, properties, isBack); } OpenSessions.RemoveAll(s => s.SessionId.Equals(session.SessionId)); try { ServerController.Instance.Log.Info("SESSION UPDATE: " + session.NowViewingBaseItem.Name); } catch { } OpenSessions.Add(session); }
// ReSharper disable once FlagArgument public async Task <string> BuildAlexaResponseAsync(IResponse response, IAlexaSession session) { if (!(response.outputSpeech is null)) { var outputSpeech = response.outputSpeech; var speech = new StringBuilder(); speech.Append(outputSpeech.sound); speech.Append(Ssml.InsertStrengthBreak(StrengthBreak.strong)); speech.Append(outputSpeech.phrase); outputSpeech.ssml = "<speak>"; outputSpeech.ssml += speech.ToString(); outputSpeech.ssml += "</speak>"; } response.reprompt = new Reprompt { outputSpeech = new OutputSpeech() { ssml = "<speak>Can I help you with anything? You can ask to show a movie, or to show a tv series.</speak>" } }; // Remove the APL directive if the device doesn't handle APL. if (!session.supportsApl) { if (response.directives.Any(d => d.type == "Alexa.Presentation.APL.RenderDocument")) { response.directives.RemoveAll(d => d.type == "Alexa.Presentation.APL.RenderDocument"); } } return(await Task.FromResult(JsonSerializer.SerializeToString(new AlexaResponse() { version = "1.2", response = response }))); }
public async Task <string> RequestRoom(IAlexaRequest alexaRequest, IAlexaSession session) { var genericLayoutProperties = await DataSourcePropertiesManager.Instance.GetGenericViewPropertiesAsync("Which room did you want?", "/Question"); var aplaDataSource = await DataSourcePropertiesManager.Instance.GetAudioResponsePropertiesAsync(new InternalAudioResponseQuery() { SpeechResponseType = SpeechResponseType.RoomContext }); session.PersistedRequestData = alexaRequest; AlexaSessionManager.Instance.UpdateSession(session, null); return(await AlexaResponseClient.Instance.BuildAlexaResponseAsync(new Response() { shouldEndSession = false, directives = new List <IDirective>() { await RenderDocumentDirectiveManager.Instance.RenderVisualDocumentDirectiveAsync(genericLayoutProperties, session), await RenderDocumentDirectiveManager.Instance.RenderAudioDocumentDirectiveAsync(aplaDataSource) } }, session)); }
protected static void ItemBrowse(StringBuilder speech, BaseItem item, IAlexaSession session, bool deviceAvailable = true) { if (deviceAvailable == false) { speech.Append(GetSpeechPrefix(SpeechPrefix.DYSFLUENCY_NEGATIVE)); speech.Append(InsertStrengthBreak(StrengthBreak.weak)); speech.Append(GetSpeechPrefix(SpeechPrefix.APOLOGETIC)); speech.Append(InsertStrengthBreak(StrengthBreak.weak)); speech.Append("That device is currently unavailable."); return; } //speech.Append(GetSpeechPrefix(SpeechPrefix.COMPLIANCE)); speech.Append(InsertStrengthBreak(StrengthBreak.weak)); if (RandomIndex.NextDouble() > 0.5 && !item.IsFolder) //Randomly incorporate "Here is.." into phrasing { speech.Append("Here is "); } var name = StringNormalization.ValidateSpeechQueryString(item.Name); speech.Append(name); if (!item.IsFolder) //Don't describe a rating of a library or collection folder. { var rating = item.OfficialRating; speech.Append(", "); speech.Append(string.IsNullOrEmpty(rating) ? "unrated" : $"Rated {rating}"); } if (!session.hasRoom) { return; } speech.Append(InsertStrengthBreak(StrengthBreak.weak)); speech.Append("Showing in the "); speech.Append(session.room.Name); }
public async Task BrowseItemAsync(IAlexaSession alexaSession, BaseItem request) { string deviceId; try { deviceId = ServerDataQuery.Instance.GetDeviceIdFromRoomName(alexaSession.room.Name); } catch (Exception ex) { throw new Exception(ex.Message); } var session = ServerDataQuery.Instance.GetSession(deviceId); var type = request.GetType().Name; // ReSharper disable once ComplexConditionExpression if (!type.Equals("Season") || !type.Equals("Series")) { await BrowseHome(alexaSession.room.Name, alexaSession.User, deviceId, session); } try { #pragma warning disable 4014 SessionManager.SendBrowseCommand(null, session.Id, new BrowseRequest() #pragma warning restore 4014 { ItemId = request.Id.ToString(), ItemName = request.Name, ItemType = request.MediaType }, CancellationToken.None); } catch { throw new BrowseCommandException($"I was unable to browse to {request.Name}."); } }
public MoviesIntent(IAlexaRequest alexaRequest, IAlexaSession session) { AlexaRequest = alexaRequest; Session = session; }
public TvShowsIntent(IAlexaRequest alexaRequest, IAlexaSession session) { AlexaRequest = alexaRequest; Session = session; }
public async Task <IDirective> RenderVisualDocumentDirectiveAsync <T>(Properties <T> properties, IAlexaSession session) where T : class { var sourcesTemplate = new SourcesTemplate(); //Button click effect sourcesTemplate.Add("buttonPressEffectApla", new Alexa.Presentation.APLA.Document() { mainTemplate = new MainTemplate() { parameters = new List <string>() { "payload" }, item = new Audio() { source = "soundbank://soundlibrary/camera/camera_15", filter = new List <IFilter>() { new Volume() { amount = 0.2 } } } } }); var dataSourceTemplate = new DataSourceTemplate(); dataSourceTemplate.Add(properties); switch (properties?.documentType) { case RenderDocumentType.MEDIA_ITEM_DETAILS_TEMPLATE: dataSourceTemplate.Add(new TextToSpeechTransformer() { inputPath = "item.overview", outputName = "readOverview" }); dataSourceTemplate.Add(new AplaSpeechTransformer() { template = "buttonPressEffectApla", outputName = "buttonPressEffect" }); break; case RenderDocumentType.MEDIA_ITEM_LIST_SEQUENCE_TEMPLATE: dataSourceTemplate.Add(new AplaSpeechTransformer() { template = "buttonPressEffectApla", outputName = "buttonPressEffect" }); break; case RenderDocumentType.HELP_TEMPLATE: dataSourceTemplate.Add(new TextToSpeechTransformer() { inputPath = "values[*].value", outputName = "helpPhrase" }); break; case RenderDocumentType.GENERIC_VIEW_TEMPLATE: break; case RenderDocumentType.ROOM_SELECTION_TEMPLATE: break; default: return(null); } return(await Task.FromResult(new AplRenderDocumentDirective() { token = properties.documentType.ToString(), // ReSharper disable once RedundantNameQualifier document = new Alexa.Presentation.APL.Document() { theme = "${payload.templateData.properties.theme}", extensions = ExtensionsManager.RenderExtensionsList(session.context), settings = SettingsManager.RenderSettings(session.context), import = ImportsManager.RenderImportsList, resources = ResourcesManager.RenderResourcesList, graphics = VectorGraphicsManager.RenderVectorGraphicsDictionary, commands = new Dictionary <string, ICommand>() { { nameof(Animations.ScaleInOutOnPress), await Animations.ScaleInOutOnPress() }, { nameof(Animations.FadeIn), await Animations.FadeIn() }, { "ShakesHead", new PlayNamedChoreo() { name = Choreo.MixedExpressiveShakes } } }, mainTemplate = new MainTemplate() { parameters = new List <string>() { "payload" }, items = await Layouts.RenderLayoutComponents(properties, session) } }, datasources = new DataSource() { templateData = await dataSourceTemplate.BuildTemplateData() }, sources = await sourcesTemplate.BuildSources() })); }
protected static void ParentalControlNotAllowed(StringBuilder speech, BaseItem item, IAlexaSession session) { speech.Append("<say-as interpret-as=\"interjection\">mm hmm</say-as>"); speech.Append(InsertStrengthBreak(StrengthBreak.weak)); speech.Append("Are you sure you are allowed access to "); speech.Append(item is null ? "this item" : item.Name); speech.Append(InsertStrengthBreak(StrengthBreak.weak)); speech.Append(SayName(session.context.System.person)); speech.Append("?"); }
protected static void VoiceAuthenticationAccountLinkSuccess(StringBuilder speech, IAlexaSession session) { speech.Append(ExpressiveInterjection("Success ")); speech.Append(ExpressiveInterjection(SayName(session.context.System.person))); speech.Append("!"); speech.Append("Please look at the plugin configuration. "); speech.Append("You should now see the I.D. linked to your voice."); speech.Append(InsertStrengthBreak(StrengthBreak.weak)); speech.Append("Choose your emby account name and press save. "); }
protected static void NewLibraryItems(StringBuilder speech, List <BaseItem> items, DateTime date, IAlexaSession session) { speech.Append("There "); speech.Append(items?.Count > 1 ? "are" : "is"); speech.Append(SayAsCardinal(items?.Count.ToString())); speech.Append(" new "); speech.Append(items?.Count > 1 ? items[0].GetType().Name + "s" : items?[0].GetType().Name); //var date = DateTime.Parse(query.args[0]); speech.Append($" added in the past {(date - DateTime.Now).Days * -1} days. "); if (!session.supportsApl) { speech.Append(string.Join($", {InsertStrengthBreak(StrengthBreak.weak)}", // ReSharper disable once AssignNullToNotNullAttribute items?.ToArray().Select(item => StringNormalization.ValidateSpeechQueryString(item.Name)))); } }
private static async Task <string> GetResponseResult(Type type, IAlexaRequest alexaRequest, IAlexaSession session) { var paramArgs = session is null ? new object[] { alexaRequest } : new object[] { alexaRequest, session }; var instance = Activator.CreateInstance(type, paramArgs); return(await(Task <string>) type.GetMethod("Response").Invoke(instance, null)); }
public async Task <Properties <MediaItem> > GetRoomSelectionViewPropertiesAsync(BaseItem item, IAlexaSession session) { return(await Task.FromResult(new Properties <MediaItem>() { url = await ServerDataQuery.Instance.GetLocalApiUrlAsync(), wanAddress = await ServerDataQuery.Instance.GetWanAddressAsync(), documentType = RenderDocumentType.ROOM_SELECTION_TEMPLATE, item = new MediaItem() { type = item.GetType().Name, isPlayed = item.IsPlayed(session.User), primaryImageSource = ServerDataQuery.Instance.GetPrimaryImageSource(item), id = item.InternalId, name = item.Name, premiereDate = item.ProductionYear.ToString(), officialRating = item.OfficialRating, tagLine = item.Tagline, runtimeMinutes = ServerDataQuery.Instance.GetRunTime(item), endTime = ServerDataQuery.Instance.GetEndTime(item), genres = ServerDataQuery.Instance.GetGenres(item), logoImageSource = ServerDataQuery.Instance.GetLogoImageSource(item), overview = item.Overview, videoBackdropSource = ServerDataQuery.Instance.GetVideoBackdropImageSource(item), backdropImageSource = ServerDataQuery.Instance.GetBackdropImageSource(item) } })); }
private async Task <string> OnIntentRequest(IAlexaRequest alexaRequest) { IAlexaSession session = null; var request = alexaRequest.request; var intent = request.intent; var context = alexaRequest.context; var system = context.System; var person = system.person; if (!IsVoiceAuthenticationAccountLinkRequest(intent)) // create a session { if (!(person is null)) { if (!SpeechAuthorization.Instance.UserPersonalizationProfileExists(person)) { return(await AlexaResponseClient.Instance.BuildAlexaResponseAsync(new Response() { shouldEndSession = true, outputSpeech = new OutputSpeech() { phrase = "You are not a recognized user. Please take moment to register your voice profile.", } }, null)); } } var user = SpeechAuthorization.Instance.GetRecognizedPersonalizationProfileResult(person); session = AlexaSessionManager.Instance.GetSession(alexaRequest, user); //There can not be a room intent request without any prior session context data. if (session.PersistedRequestData is null && IsRoomNameIntentRequest(intent)) { //end the session. return(await new NotUnderstood(alexaRequest, session).Response()); } } try { /* * Amazon Alexa Custom SKill Console does not allow "." in skill names. * This would make creating namespace paths easier. * Instead we save the skill name with "_", which replaces the "." in the reflected path to the corresponding .cs file. * Replace the "_" (underscore) with a "." (period) to create the proper reflection path to the corresponding IntentRequest file. */ var intentName = intent.name.Replace("_", "."); ServerController.Instance.Log.Info($"Intent Name Route to {intentName}"); return(await GetResponseResult(Type.GetType($"AlexaController.Api.IntentRequest.{intentName}"), alexaRequest, session)); } catch (Exception exception) { var dataSource = await DataSourcePropertiesManager.Instance.GetGenericViewPropertiesAsync(exception.Message, "/particles"); return(await AlexaResponseClient.Instance.BuildAlexaResponseAsync(new Response() { shouldEndSession = true, outputSpeech = new OutputSpeech() { phrase = $"{Ssml.SayWithEmotion($"Sorry, I was unable to do that. {exception.Message}", Emotion.excited, Intensity.low)}", }, directives = new List <IDirective>() { await RenderDocumentDirectiveManager.Instance.RenderVisualDocumentDirectiveAsync(dataSource, session) } }, session)); } }
public CancelIntent(IAlexaRequest alexaRequest, IAlexaSession session) { AlexaRequest = alexaRequest; Session = session; }
public IAlexaSession GetSession(IAlexaRequest alexaRequest, User user = null) { // A UserEvent can only happen in an open session because sessions will always start with voice. if (string.Equals(alexaRequest.request.type, "Alexa.Presentation.APL.UserEvent")) { return(OpenSessions.FirstOrDefault(s => s.SessionId == alexaRequest.session.sessionId)); } var context = alexaRequest.context; var system = context.System; //var person = system.person; var amazonSession = alexaRequest.session; IAlexaRequest persistedRequestData = null; IAlexaSession sessionInfo = null; //Room room = null; if (OpenSessions.Exists(s => s.SessionId.Equals(amazonSession.sessionId))) { sessionInfo = OpenSessions.FirstOrDefault(s => s.SessionId == amazonSession.sessionId); persistedRequestData = sessionInfo?.PersistedRequestData; //room = sessionInfo?.room; // ReSharper disable once ComplexConditionExpression //if (!(person is null) && !(sessionInfo?.person is null)) //{ // if (string.Equals(sessionInfo.person.personId, person.personId)) // { // return sessionInfo; // It is the same person speaking - return the sessionInfo. // } //} // Remove the session from the "OpenSessions" List, and rebuild the session with the new data OpenSessions.RemoveAll(s => s.SessionId.Equals(alexaRequest.session.sessionId)); } // Sync AMAZON session Id with our own. sessionInfo = new AlexaSession() { SessionId = amazonSession.sessionId, EmbySessionId = !(sessionInfo?.room is null) ? GetCorrespondingEmbySessionId(sessionInfo) : string.Empty, context = context, EchoDeviceId = system.device.deviceId, NowViewingBaseItem = sessionInfo?.NowViewingBaseItem, supportsApl = SupportsApl(alexaRequest), room = sessionInfo?.room, hasRoom = !(sessionInfo?.room is null), User = user, viewport = GetCurrentViewport(alexaRequest), PersistedRequestData = persistedRequestData, paging = new Paging { pages = new Dictionary <int, Properties <MediaItem> >() } }; OpenSessions.Add(sessionInfo); return(sessionInfo); }
protected static void BrowseNextUpEpisode(StringBuilder speech, BaseItem item, IAlexaSession session) { var season = item.Parent; var seriesName = season?.Parent.Name; speech.Append("Here is the next up episode for "); speech.Append(seriesName); speech.Append(InsertStrengthBreak(StrengthBreak.weak)); speech.Append(item.Name); if (session.room is null) { return; } speech.Append(InsertStrengthBreak(StrengthBreak.weak)); speech.Append("Showing in the "); speech.Append(session.room.Name); }
public async Task <Properties <MediaItem> > GetBaseItemDetailViewPropertiesAsync(BaseItem item, IAlexaSession session) { var mediaItem = new MediaItem() { type = item.GetType().Name, isPlayed = item.IsPlayed(session.User), primaryImageSource = ServerDataQuery.Instance.GetPrimaryImageSource(item), id = item.InternalId, name = item.Name, premiereDate = item.ProductionYear.ToString(), officialRating = item.OfficialRating, tagLine = item.Tagline, runtimeMinutes = ServerDataQuery.Instance.GetRunTime(item), endTime = ServerDataQuery.Instance.GetEndTime(item), genres = ServerDataQuery.Instance.GetGenres(item), logoImageSource = ServerDataQuery.Instance.GetLogoImageSource(item), overview = item.Overview, videoBackdropSource = ServerDataQuery.Instance.GetVideoBackdropImageSource(item), backdropImageSource = ServerDataQuery.Instance.GetBackdropImageSource(item), videoOverlaySource = "/EmptyPng?quality=90", themeAudioSource = ServerDataQuery.Instance.GetThemeSongSource(item), TotalRecordCount = item.GetType().Name == "Series" ? ServerDataQuery.Instance.GetItemsResult(item.InternalId, new[] { "Season" }, session.User).TotalRecordCount : 0, chapterData = item.GetType().Name == "Movie" || item.GetType().Name == "Episode" ? ServerDataQuery.Instance.GetChapterInfo(item) : null, Resolution = ServerDataQuery.Instance.GetResolution(item) }; var similarItems = ServerDataQuery.Instance.GetSimilarItems(item); var recommendedItems = new List <MediaItem>(); similarItems.ForEach(r => recommendedItems.Add(new MediaItem() { id = r.InternalId, thumbImageSource = ServerDataQuery.Instance.GetThumbImageSource(r) })); return(await Task.FromResult(new Properties <MediaItem>() { url = await ServerDataQuery.Instance.GetLocalApiUrlAsync(), wanAddress = await ServerDataQuery.Instance.GetWanAddressAsync(), documentType = RenderDocumentType.MEDIA_ITEM_DETAILS_TEMPLATE, similarItems = recommendedItems, item = mediaItem })); }